1 { lib, stdenv, buildEnv, runCommand, fetchurl, file, texlive, writeShellScript, writeText, texliveInfraOnly, texliveConTeXt, texliveSmall, texliveMedium, texliveFull }:
5 mkTeXTest = lib.makeOverridable (
9 , texLive ? texliveSmall
10 , options ? "-interaction=errorstopmode"
14 }@attrs: runCommand "texlive-test-tex-${name}"
16 nativeBuildInputs = [ texLive ] ++ attrs.nativeBuildInputs or [ ];
17 text = builtins.toFile "${name}.tex" text;
18 } // builtins.removeAttrs attrs [ "nativeBuildInputs" "text" "texLive" ])
20 export HOME="$(mktemp -d)"
23 cp "$text" "$name.tex"
25 $format $options "$name.tex"
30 tlpdbNix = runCommand "texlive-test-tlpdb-nix" {
31 nixpkgsTlpdbNix = ../../tools/typesetting/tex/texlive/tlpdb.nix;
32 tlpdbNix = texlive.tlpdb.nix;
36 diff -u "''${nixpkgsTlpdbNix}" "''${tlpdbNix}" | tee "$out/tlpdb.nix.patch"
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";
46 texLive = texliveSmall.withPackages (ps: [ ps.libertinus-fonts ]);
48 \documentclass{article}
50 \setmainfont{Libertinus Serif}
56 xelatex = lualatex.override {
57 name = "opentype-fonts-xelatex";
62 chktex = runCommand "texlive-test-chktex" {
64 (texlive.withPackages (ps: [ ps.chktex ]))
66 input = builtins.toFile "chktex-sample.tex" ''
67 \documentclass{article}
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"
81 name = "texlive-test-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
87 if [[ ! -f "$name".pdf ]] ; then
88 echo "ConTeXt test failed: file '$name.pdf' not found"
94 \startsection[title={ConTeXt test document}]
95 This is an {\em incredibly} simple ConTeXt document.
101 dvipng = lib.recurseIntoAttrs {
102 # https://github.com/NixOS/nixpkgs/issues/75605
103 basic = runCommand "texlive-test-dvipng-basic" {
104 nativeBuildInputs = [ file texliveMedium ];
106 name = "test_dvipng.tex";
107 url = "http://git.savannah.nongnu.org/cgit/dvipng.git/plain/test_dvipng.tex?id=b872753590a18605260078f56cbd6f28d39dc035";
108 sha256 = "1pjpf1jvwj2pv5crzdgcrzvbmn7kfmgxa39pcvskl4pa0c9hl88n";
111 cp "$input" ./document.tex
114 dvipng -T tight -strict -picky document.dvi
115 for f in document*.png; do
116 file "$f" | tee output
121 mv document*.png "$out"/
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}
142 gs_trap = writeShellScript "gs_trap.sh" ''
147 export PATH=$PWD:$PATH
148 # check that the trap works
151 cp "$input" ./document.tex
154 dvipng -T 1in,1in -strict -picky document.dvi
155 for f in document*.png; do
156 file "$f" | tee output
161 mv document*.png "$out"/
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}
175 cp "$input" ./document.tex
178 dvisvgm document.dvi -n -o document_dvi.svg
180 file document_dvi.svg | grep SVG
182 pdflatex document.tex
183 dvisvgm -P document.pdf -n -o document_pdf.svg
185 file document_pdf.svg | grep SVG
188 mv document*.svg "$out"/
191 texdoc = runCommand "texlive-test-texdoc" {
192 nativeBuildInputs = [
193 (texlive.withPackages (ps: [ ps.luatex ps.texdoc ps.texdoc.texdoc ]))
198 texdoc --debug --list texdoc | tee "$out"
199 grep texdoc.pdf "$out"
202 # check that the default language is US English
203 defaultLanguage = lib.recurseIntoAttrs rec {
206 name = "default-language-etex";
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
219 name = "default-language-latex";
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
231 luatex = etex.override {
232 name = "default-language-luatex";
237 # check that all languages are available, including synonyms
238 allLanguages = let hyphenBase = texlive.pkgs.hyphen-base; texLive = texliveFull; in
239 lib.recurseIntoAttrs {
242 name = "all-languages-etex";
244 inherit hyphenBase texLive;
249 \ifcsname lang@#1\endcsname\message{[tests.texlive] Found language #1}
250 \else\errmessage{[tests.texlive] Error: missing language #1}\fi
252 \comma@parse{@texLanguages@}\CheckLang
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
263 name = "all-languages-latex";
265 inherit hyphenBase texLive;
268 \@for\Lang:=italian,@texLanguages@\do{
269 \ifcsname l@\Lang\endcsname
270 \GenericWarning{}{[tests.texlive] Found language \Lang}
272 \GenericError{}{[tests.texlive] Error: missing language \Lang}{}{}
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
285 name = "all-languages-luatex";
287 inherit hyphenBase texLive;
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')
297 error('[tests.texlive] Error: missing language '..l..'.', 2)
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
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;
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
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.
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"
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 ])) ];
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
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 ])) ];
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
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
371 # TODO known broken binaries
373 # *.inc files in source container rather than run
376 # 'Error initialising QuantumRenderer: no suitable pipeline found'
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"
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"
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)
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
418 # binaries that need a combined scheme and cannot work standalone
420 # pfarrei: require working kpse to find lua module
423 # bibexport: requires kpsewhich
426 # crossrefware: require bibtexperllibs under TEXMFROOT
427 "bbl2bib" "bibdoiadd" "bibmradd" "biburl2doi" "bibzbladd" "checkcites" "ltx2crossrefxml"
429 # epstopdf: requires kpsewhich
430 "epstopdf" "repstopdf"
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"
455 contextTestTex = writeText "context-test.tex" ''
460 latexTestTex = writeText "latex-test.tex" ''
461 \documentclass{article}
466 texTestTex = writeText "tex-test.tex" ''
471 # link all binaries in single derivation
472 binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs);
473 binaries = buildEnv { name = "texlive-binaries"; paths = binPackages; };
475 runCommand "texlive-test-binaries"
477 inherit binaries contextTestTex latexTestTex texTestTex;
478 texliveScheme = texliveFull;
481 loadables="$(command -v bash)"
482 loadables="''${loadables%/bin/bash}/lib/bash"
483 enable -f "$loadables/realpath" realpath
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
492 path="$(realpath "$bin")"
494 if [[ -z "$ignoreExitCode" ]] ; then
495 PATH="$path" "$bin" $args >"$out/$base.log" 2>&1
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}'"
503 PATH="$path" "$bin" $args >"$out/$base.log" 2>&1
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}'"
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}'"
516 for bin in "$binaries"/bin/* ; do
520 binCount=$((binCount + 1))
522 # ignore non-executable files (such as context.lua)
523 if [[ ! -x "$bin" ]] ; then
524 ignoredCount=$((ignoredCount + 1))
529 ${lib.concatStringsSep "|" ignored})
530 ignoredCount=$((ignoredCount + 1))
532 ${lib.concatStringsSep "|" broken})
533 brokenCount=$((brokenCount + 1))
535 ${lib.concatStringsSep "|" help})
537 ${lib.concatStringsSep "|" shortHelp})
539 ${lib.concatStringsSep "|" noArg})
541 ${lib.concatStringsSep "|" contextTest})
542 args=context-test.tex ;;
543 ${lib.concatStringsSep "|" latexTest})
544 args=latex-test.tex ;;
545 ${lib.concatStringsSep "|" texTest})
547 ${lib.concatStringsSep "|" shortVersion})
553 args='latex latex-test.tex' ;;
555 args='--help --help --help' ;;
563 ${lib.concatStringsSep "|" (ignoreExitCode ++ noArg)})
568 ${lib.concatStringsSep "|" needScheme})
569 bin="$texliveScheme/bin/$base"
570 if [[ ! -f "$bin" ]] ; then
571 ignoredCount=$((ignoredCount + 1))
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))
583 echo "tested $binCount binaries: $ignoredCount ignored, $brokenCount broken, $failedCount failed"
584 [[ $failedCount = 0 ]]
587 # check that all scripts have a Nix shebang
589 binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs);
591 runCommand "texlive-test-shebangs" { }
593 echo "checking that all texlive scripts shebangs are in '$NIX_STORE'"
594 declare -i scriptCount=0 invalidCount=0
596 (lib.concatMapStrings
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))
612 echo "checked $scriptCount scripts, found $invalidCount non-nix shebangs"
613 [[ $invalidCount -gt 0 ]] && exit 1
618 # verify that the precomputed licensing information in default.nix
619 # does indeed match the metadata of the individual packages.
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.
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));
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 [])))
639 scheme.passthru.requiredTeXPackages;
640 correctLicensesAttrNames = scheme:
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:
652 license info for ${name} is incorrect! Note that order is enforced.
653 saved: [ ${lib.concatStringsSep " " (savedLicensesAttrNames scheme)} ]
654 correct: [ ${lib.concatStringsSep " " (correctLicensesAttrNames scheme)} ]
656 errorText = lib.concatStringsSep "\n\n" (lib.mapAttrsToList prettyPrint incorrectSchemes);
658 runCommand "texlive-test-license" {
661 (if (incorrectSchemes == {})
662 then "echo everything is fine! > $out"
668 # verify that all fixed hashes are present
669 # this is effectively an eval-time assertion, converted into a derivation for
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" {
681 passAsFile = [ "errorText" ];
683 if [[ -s "$errorTextPath" ]] ; then
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.