summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/l3build/l3build-check.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/scripts/l3build/l3build-check.lua')
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-check.lua894
1 files changed, 894 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/l3build/l3build-check.lua b/Master/texmf-dist/scripts/l3build/l3build-check.lua
new file mode 100644
index 00000000000..2239fe97a76
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-check.lua
@@ -0,0 +1,894 @@
+--[[
+
+File l3build-check.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+-- Local access to functions
+local open = io.open
+local close = io.close
+local write = io.write
+local output = io.output
+
+local rnd = math.random
+
+local luatex_version = status.luatex_version
+
+local len = string.len
+local char = string.char
+local format = string.format
+local gmatch = string.gmatch
+local gsub = string.gsub
+local match = string.match
+
+local insert = table.insert
+local sort = table.sort
+
+local utf8_char = unicode.utf8.char
+
+local exit = os.exit
+local execute = os.execute
+
+--
+-- Auxiliary functions which are used by more than one main function
+--
+
+-- Set up the check system files: needed for checking one or more tests and
+-- for saving the test files
+function checkinit()
+ cleandir(testdir)
+ depinstall(checkdeps)
+ -- Copy dependencies to the test directory itself: this makes the paths
+ -- a lot easier to manage, and is important for dealing with the log and
+ -- with file input/output tests
+ for _,i in ipairs(filelist(localdir)) do
+ cp(i, localdir, testdir)
+ end
+ bundleunpack({sourcefiledir, testfiledir})
+ for _,i in ipairs(installfiles) do
+ cp(i, unpackdir, testdir)
+ end
+ for _,i in ipairs(checkfiles) do
+ cp(i, unpackdir, testdir)
+ end
+ if direxists(testsuppdir) then
+ for _,i in ipairs(filelist(testsuppdir)) do
+ cp(i, testsuppdir, testdir)
+ end
+ end
+ for _,i in ipairs(checksuppfiles) do
+ cp(i, supportdir, testdir)
+ end
+ execute(os_ascii .. ">" .. testdir .. "/ascii.tcx")
+end
+
+-- Convert the raw log file into one for comparison/storage: keeps only
+-- the 'business' part from the tests and removes system-dependent stuff
+local function formatlog(logfile, newfile, engine, errlevels)
+ local maxprintline = maxprintline
+ if engine == "luatex" or engine == "luajittex" then
+ maxprintline = maxprintline + 1 -- Deal with an out-by-one error
+ end
+ local function killcheck(line)
+ -- Skip lines containing file dates
+ if match(line, "[^<]%d%d%d%d/%d%d/%d%d") then
+ return true
+ elseif
+ -- Skip \openin/\openout lines in web2c 7.x
+ -- As Lua doesn't allow "(in|out)", a slightly complex approach:
+ -- do a substitution to check the line is exactly what is required!
+ match(
+ gsub(line, "^\\openin", "\\openout"), "^\\openout%d%d? = "
+ ) then
+ return true
+ end
+ return false
+ end
+ -- Substitutions to remove some non-useful changes
+ local function normalize(line, lastline)
+ -- Zap line numbers from \show, \showbox, \box_show and the like:
+ -- do this before wrapping lines
+ line = gsub(line, "^l%.%d+ ", "l. ...")
+ -- Also from lua stack traces.
+ line = gsub(line, "lua:%d+: in function", "lua:...: in function")
+ -- Allow for wrapped lines: preserve the content and wrap
+ -- Skip lines that have an explicit marker for truncation
+ if len(line) == maxprintline and
+ not match(line, "%.%.%.$") then
+ return "", (lastline or "") .. line
+ end
+ local line = (lastline or "") .. line
+ lastline = ""
+ -- Zap ./ at begin of filename
+ line = gsub(line, "%(%.%/", "(")
+ -- Zap paths if places other than 'here' are accessible
+ if checksearch then
+ -- The pattern excludes < and > as the image part can have
+ -- several entries on one line
+ local pattern = "%w?:?/[^ %<%>]*/([^/%(%)]*%.%w*)"
+ -- Files loaded from TeX: all start ( -- )
+ line = gsub(line, "%(" .. pattern, "(../%1")
+ -- Images
+ line = gsub(line, "<" .. pattern .. ">", "<../%1>")
+ -- luaotfload files start with keywords
+ line = gsub(line, "from " .. pattern .. "%(", "from. ./%1(")
+ line = gsub(line, ": " .. pattern .. "%)", ": ../%1)")
+ -- Deal with XeTeX specials
+ if match(line, "^%.+\\XeTeX.?.?.?file") then
+ line = gsub(line, pattern, "../%1")
+ end
+ end
+ -- Deal with the fact that "(.aux)" may have still a leading space
+ line = gsub(line, "^ %(%.aux%)", "(.aux)")
+ -- Merge all of .fd data into one line so will be removed later
+ if match(line, "^ *%([%.%/%w]+%.fd[^%)]*$") then
+ lastline = (lastline or "") .. line
+ return "", (lastline or "") .. line
+ end
+ -- TeX90/XeTeX knows only the smaller set of dimension units
+ line = gsub(line,
+ "cm, mm, dd, cc, bp, or sp",
+ "cm, mm, dd, cc, nd, nc, bp, or sp")
+ -- On the other hand, (u)pTeX has some new units!
+ line = gsub(line,
+ "em, ex, zw, zh, in, pt, pc,",
+ "em, ex, in, pt, pc,")
+ line = gsub(line,
+ "cm, mm, dd, cc, bp, H, Q, or sp;",
+ "cm, mm, dd, cc, nd, nc, bp, or sp;")
+ -- Normalise a case where fixing a TeX bug changes the message text
+ line = gsub(line, "\\csname\\endcsname ", "\\csname\\endcsname")
+ -- Zap "on line <num>" and replace with "on line ..."
+ -- Two similar cases, Lua patterns mean we need to do them separately
+ line = gsub(line, "on line %d*", "on line ...")
+ line = gsub(line, "on input line %d*", "on input line ...")
+ -- Tidy up to ^^ notation
+ for i = 0, 31 do
+ line = gsub(line, char(i), "^^" .. char(64 + i))
+ end
+ -- Normalise register allocation to hard-coded numbers
+ -- No regex, so use a pattern plus lookup approach
+ local register_types = {
+ attribute = true,
+ box = true,
+ bytecode = true,
+ catcodetable = true,
+ count = true,
+ dimen = true,
+ insert = true,
+ language = true,
+ luabytecode = true,
+ luachunk = true,
+ luafunction = true,
+ marks = true,
+ muskip = true,
+ read = true,
+ skip = true,
+ toks = true,
+ whatsit = true,
+ write = true,
+ XeTeXcharclass = true
+ }
+ if register_types[match(line, "^\\[^%]]+=\\([a-z]+)%d+$")] then
+ line = gsub(line, "%d+$", "...")
+ end
+ -- Also deal with showing boxes
+ if match(line, "^> \\box%d+=$") or match(line, "^> \\box%d+=(void)$") then
+ line = gsub(line, "%d+=", "...=")
+ end
+ -- Remove 'normal' direction information on boxes with (u)pTeX
+ line = gsub(line, ",? yoko direction,?", "")
+ line = gsub(line, ",? yoko%(math%) direction,?", "")
+ -- Remove '\displace 0.0' lines in (u)pTeX
+ if match(line,"^%.*\\displace 0%.0$") then
+ return ""
+ end
+ -- Remove the \special line that in DVI mode keeps PDFs comparable
+ if match(line, "^%.*\\special%{pdf: docinfo << /Creator") then
+ return ""
+ end
+ -- Remove the \special line possibly present in DVI mode for paper size
+ if match(line, "^%.*\\special%{papersize") then
+ return ""
+ end
+ -- Remove ConTeXt stuff
+ if match(line, "^backend >") or
+ match(line, "^close source >") or
+ match(line, "^mkiv lua stats >") or
+ match(line, "^pages >") or
+ match(line, "^system >") or
+ match(line, "^used file >") or
+ match(line, "^used option >") or
+ match(line, "^used structure >") then
+ return ""
+ end
+ -- A tidy-up to keep LuaTeX and other engines in sync
+ line = gsub(line, utf8_char(127), "^^?")
+ -- Unicode engines display chars in the upper half of the 8-bit range:
+ -- tidy up to match pdfTeX if an ASCII engine is in use
+ if next(asciiengines) then
+ for i = 128, 255 do
+ line = gsub(line, utf8_char(i), "^^" .. format("%02x", i))
+ end
+ end
+ return line, lastline
+ end
+ local lastline = ""
+ local newlog = ""
+ local prestart = true
+ local skipping = false
+ -- Read the entire log file as a binary: deals with ^@/^[, etc.
+ local file = assert(open(logfile, "rb"))
+ local contents = gsub(file:read("*all") .. "\n", "\r\n", "\n")
+ close(file)
+ for line in gmatch(contents, "([^\n]*)\n") do
+ if line == "START-TEST-LOG" then
+ prestart = false
+ elseif line == "END-TEST-LOG" or
+ match(line, "^Here is how much of .?.?.?TeX\'s memory you used:") then
+ break
+ elseif line == "OMIT" then
+ skipping = true
+ elseif match(line, "^%)?TIMO$") then
+ skipping = false
+ elseif not prestart and not skipping then
+ line, lastline = normalize(line, lastline)
+ if not match(line, "^ *$") and not killcheck(line) then
+ newlog = newlog .. line .. os_newline
+ end
+ end
+ end
+ local newfile = open(newfile, "w")
+ output(newfile)
+ write(newlog)
+ if recordstatus then
+ write('***************\n')
+ for i = 1, checkruns do
+ write('Compilation ' .. i .. ' of test file completed with exit status ' .. errlevels[i] .. '\n')
+ end
+ end
+ close(newfile)
+end
+
+-- Additional normalization for LuaTeX
+local function formatlualog(logfile, newfile, luatex)
+ local function normalize(line, lastline, dropping)
+ -- Find \discretionary or \whatsit lines:
+ -- These may come back later
+ if match(line, "^%.+\\discretionary$") or
+ match(line, "^%.+\\discretionary %(penalty 50%)$") or
+ match(line, "^%.+\\discretionary50%|$") or
+ match(line, "^%.+\\discretionary50%| replacing $") or
+ match(line, "^%.+\\whatsit$") then
+ return "", line
+ end
+ -- For \mathon, we always need this line but the next
+ -- may be affected
+ if match(line, "^%.+\\mathon$") then
+ return line, line
+ end
+ -- LuaTeX has a flexible output box
+ line = gsub(line,"\\box\\outputbox", "\\box255")
+ -- LuaTeX identifies spaceskip glue
+ line = gsub(line,"%(\\spaceskip%) ", " ")
+ -- Remove 'display' at end of display math boxes:
+ -- LuaTeX omits this as it includes direction in all cases
+ line = gsub(line, "(\\hbox%(.*), display$", "%1")
+ -- Remove 'normal' direction information on boxes:
+ -- any bidi/vertical stuff will still show
+ line = gsub(line, ", direction TLT", "")
+ -- Find glue setting and round out the last place
+ local function round_digits(l, m)
+ return gsub(
+ l,
+ m .. " (%-?)%d+%.%d+",
+ m .. " %1"
+ .. format(
+ "%.3f",
+ match(line, m .. " %-?(%d+%.%d+)") or 0
+ )
+ )
+ end
+ if match(line, "glue set %-?%d+%.%d+") then
+ line = round_digits(line, "glue set")
+ end
+ if match(
+ line, "glue %-?%d+%.%d+ plus %-?%d+%.%d+ minus %-?%d+%.%d+$"
+ )
+ then
+ line = round_digits(line, "glue")
+ line = round_digits(line, "plus")
+ line = round_digits(line, "minus")
+ end
+ -- LuaTeX writes ^^M as a new line, which we lose
+ line = gsub(line, "%^%^M", "")
+ -- Remove U+ notation in the "Missing character" message
+ line = gsub(
+ line,
+ "Missing character: There is no (%^%^..) %(U%+(....)%)",
+ "Missing character: There is no %1"
+ )
+ -- The first time a new font is used, it shows up
+ -- as being cached
+ line = gsub(line, "(save cache:", "(load cache:")
+ -- LuaTeX from v1.07 logs kerns differently ...
+ -- This block only applies to the output of LuaTeX itself,
+ -- hence needing a flag to skip the case of the reference log
+ if luatex and
+ tonumber(luatex_version) >= 107 and
+ match(line, "^%.*\\kern") then
+ -- Re-insert the space in explicit kerns
+ if match(line, "kern%-?%d+%.%d+ *$") then
+ line = gsub(line, "kern", "kern ")
+ elseif match(line, "%(accent%)$") then
+ line = gsub(line, "kern", "kern ")
+ line = gsub(line, "%(accent%)$", "(for accent)")
+ elseif match(line, "%(italic%)$") then
+ line = gsub(line, "kern", "kern ")
+ line = gsub(line, " %(italic%)$", "")
+ else
+ line = gsub(line, " %(font%)$", "")
+ end
+ end
+ -- Changes in PDF specials
+ line = gsub(line, "\\pdfliteral origin", "\\pdfliteral")
+ -- A function to handle the box prefix part
+ local function boxprefix(s)
+ return gsub(match(s, "^(%.+)"), "%.", "%%.")
+ end
+ -- 'Recover' some discretionary data
+ if match(lastline, "^%.+\\discretionary %(penalty 50%)$") and
+ match(line, boxprefix(lastline) .. "%.= ") then
+ line = gsub(line," %(font%)$","")
+ return gsub(line, "%.= ", ""),""
+ end
+ -- Where the last line was a discretionary, looks for the
+ -- info one level in about what it represents
+ if match(lastline, "^%.+\\discretionary$") or
+ match(lastline, "^%.+\\discretionary %(penalty 50%)$") or
+ match(lastline, "^%.+\\discretionary50%|$") or
+ match(lastline, "^%.+\\discretionary50%| replacing $") then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "%.") or
+ match(line, prefix .. "%|") then
+ if match(lastline, " replacing $") and
+ not dropping then
+ -- Modify the return line
+ return gsub(line, "^%.", ""), lastline, true
+ else
+ return "", lastline, true
+ end
+ else
+ if dropping then
+ -- End of a \discretionary block
+ return line, ""
+ else
+ -- Not quite a normal discretionary
+ if match(lastline, "^%.+\\discretionary50%|$") then
+ lastline = gsub(lastline, "50%|$", "")
+ end
+ -- Remove some info that TeX90 lacks
+ lastline = gsub(lastline, " %(penalty 50%)$", "")
+ -- A normal (TeX90) discretionary:
+ -- add with the line break reintroduced
+ return lastline .. os_newline .. line, ""
+ end
+ end
+ end
+ -- Look for another form of \discretionary, replacing a "-"
+ pattern = "^%.+\\discretionary replacing *$"
+ if match(line, pattern) then
+ return "", line
+ else
+ if match(lastline, pattern) then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "%.\\kern") then
+ return gsub(line, "^%.", ""), lastline, true
+ elseif dropping then
+ return "", ""
+ else
+ return lastline .. os_newline .. line, ""
+ end
+ end
+ end
+ -- For \mathon, if the current line is an empty \hbox then
+ -- drop it
+ if match(lastline, "^%.+\\mathon$") then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "\\hbox%(0%.0%+0%.0%)x0%.0$") then
+ return "", ""
+ end
+ end
+ -- Various \local... things that other engines do not do:
+ -- Only remove the no-op versions
+ if match(line, "^%.+\\localpar$") or
+ match(line, "^%.+\\localinterlinepenalty=0$") or
+ match(line, "^%.+\\localbrokenpenalty=0$") or
+ match(line, "^%.+\\localleftbox=null$") or
+ match(line, "^%.+\\localrightbox=null$") then
+ return "", ""
+ end
+ -- Older LuaTeX versions set the above up as a whatsit
+ -- (at some stage this can therefore go)
+ if match(lastline, "^%.+\\whatsit$") then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "%.") then
+ return "", lastline, true
+ else
+ -- End of a \whatsit block
+ return line, ""
+ end
+ end
+ -- Wrap some cases that can be picked out
+ -- In some places LuaTeX does use max_print_line, then we
+ -- get into issues with different wrapping approaches
+ if len(line) == maxprintline then
+ return "", lastline .. line
+ elseif len(lastline) == maxprintline then
+ if match(line, "\\ETC%.%}$") then
+ -- If the line wrapped at \ETC we might have lost a space
+ return lastline
+ .. ((match(line, "^\\ETC%.%}$") and " ") or "")
+ .. line, ""
+ elseif match(line, "^%}%}%}$") then
+ return lastline .. line, ""
+ else
+ return lastline .. os_newline .. line, ""
+ end
+ -- Return all of the text for a wrapped (multi)line
+ elseif len(lastline) > maxprintline then
+ return lastline .. line, ""
+ end
+ -- Remove spaces at the start of lines: deals with the fact that LuaTeX
+ -- uses a different number to the other engines
+ return gsub(line, "^%s+", ""), ""
+ end
+ local newlog = ""
+ local lastline = ""
+ local dropping = false
+ -- Read the entire log file as a binary: deals with ^@/^[, etc.
+ local file = assert(open(logfile, "rb"))
+ local contents = gsub(file:read("*all") .. "\n", "\r\n", "\n")
+ close(file)
+ for line in gmatch(contents, "([^\n]*)\n") do
+ line, lastline, dropping = normalize(line, lastline, dropping)
+ if not match(line, "^ *$") then
+ newlog = newlog .. line .. os_newline
+ end
+ end
+ local newfile = open(newfile, "w")
+ output(newfile)
+ write(newlog)
+ close(newfile)
+end
+
+-- Run one test which may have multiple engine-dependent comparisons
+-- Should create a difference file for each failed test
+function runcheck(name, hide)
+ local checkengines = checkengines
+ if options["engine"] then
+ checkengines = options["engine"]
+ end
+ local errorlevel = 0
+ for _,i in ipairs(checkengines) do
+ -- Allow for luatex == luajittex for .tlg purposes
+ local engine = i
+ if i == "luajittex" then
+ engine = "luatex"
+ end
+ checkpdf = setup_check(name, engine)
+ runtest(name, i, hide, lvtext, checkpdf)
+ -- Generation of results depends on test type
+ local errlevel
+ if checkpdf then
+ errlevel = compare_pdf(name, engine)
+ else
+ errlevel = compare_tlg(name, engine)
+ end
+ if errlevel ~= 0 and options["halt-on-error"] then
+ showfaileddiff()
+ if errlevel ~= 0 then
+ return 1
+ end
+ end
+ if errlevel > errorlevel then
+ errorlevel = errlevel
+ end
+ end
+ return errorlevel
+end
+
+function setup_check(name, engine)
+ local testname = name .. "." .. engine
+ local pdffile = locate(
+ {testfiledir, unpackdir},
+ {testname .. pdfext, name .. pdfext}
+ )
+ local tlgfile = locate(
+ {testfiledir, unpackdir},
+ {testname .. tlgext, name .. tlgext}
+ )
+ -- Attempt to generate missing reference file from expectation
+ if not (pdffile or tlgfile) then
+ if not locate({unpackdir, testfiledir}, {name .. lveext}) then
+ print(
+ "Error: failed to find " .. pdfext .. ", " .. tlgext .. " or "
+ .. lveext .. " file for " .. name .. "!"
+ )
+ exit(1)
+ end
+ runtest(name, engine, true, lveext, true)
+ pdffile = testdir .. "/" .. testname .. pdfext
+ -- If a PDF is generated use it for comparisons
+ if not fileexists(pdffile) then
+ pdffile = nil
+ ren(testdir, testname .. logext, testname .. tlgext)
+ end
+ else
+ -- Install comparison files found
+ for _,v in pairs({pdffile, tlgfile}) do
+ if v then
+ cp(
+ match(v, ".*/(.*)"),
+ match(v, "(.*)/.*"),
+ testdir
+ )
+ end
+ end
+ end
+ if pdffile then
+ local pdffile = match(pdffile, ".*/(.*)")
+ ren(
+ testdir,
+ pdffile,
+ gsub(pdffile, pdfext .. "$", ".ref" .. pdfext)
+ )
+ return true
+ else
+ return false
+ end
+end
+
+function compare_pdf(name, engine)
+ local errorlevel
+ local testname = name .. "." .. engine
+ local cmpfile = testdir .. "/" .. testname .. os_cmpext
+ local pdffile = testdir .. "/" .. testname .. pdfext
+ local refpdffile = locate(
+ {testdir}, {testname .. ".ref" .. pdfext, name .. ".ref" .. pdfext}
+ )
+ if not refpdffile then
+ return
+ end
+ errorlevel = execute(
+ os_cmpexe .. " " .. normalize_path(refpdffile)
+ .. " " .. pdffile .. " > " .. cmpfile
+ )
+ if errorlevel == 0 then
+ os.remove(cmpfile)
+ end
+ return errorlevel
+end
+
+function compare_tlg(name, engine)
+ local errorlevel
+ local testname = name .. "." .. engine
+ local difffile = testdir .. "/" .. testname .. os_diffext
+ local logfile = testdir .. "/" .. testname .. logext
+ local tlgfile = locate({testdir}, {testname .. tlgext, name .. tlgext})
+ if not tlgfile then
+ return
+ end
+ -- Do additional log formatting if the engine is LuaTeX, there is no
+ -- LuaTeX-specific .tlg file and the default engine is not LuaTeX
+ if engine == "luatex"
+ and not match(tlgfile, "%.luatex" .. "%" .. tlgext)
+ and stdengine ~= "luatex"
+ and stdengine ~= "luajittex"
+ then
+ local luatlgfile = testdir .. "/" .. name .. ".luatex" .. tlgext
+ formatlualog(tlgfile, luatlgfile, false)
+ formatlualog(logfile, logfile, true)
+ -- This allows code sharing below: we only need the .tlg name in one place
+ tlgfile = luatlgfile
+ end
+ errorlevel = execute(os_diffexe .. " "
+ .. normalize_path(tlgfile .. " " .. logfile .. " > " .. difffile))
+ if errorlevel == 0 then
+ os.remove(difffile)
+ end
+ return errorlevel
+end
+
+-- Run one of the test files: doesn't check the result so suitable for
+-- both creating and verifying .tlg files
+function runtest(name, engine, hide, ext, makepdf)
+ local lvtfile = name .. (ext or lvtext)
+ cp(lvtfile, fileexists(testfiledir .. "/" .. lvtfile)
+ and testfiledir or unpackdir, testdir)
+ local engine = engine or stdengine
+ -- Set up the format file name if it's one ending "...tex"
+ local realengine = engine
+ local format
+ if
+ match(checkformat, "tex$") and
+ not match(engine, checkformat) then
+ format = " -fmt=" .. gsub(engine, "(.*)tex$", "%1") .. checkformat
+ else
+ format = ""
+ end
+ -- Special casing for e-LaTeX format
+ if
+ match(checkformat, "^latex$") and
+ match(engine, "^etex$") then
+ format = " -fmt=latex"
+ end
+ -- Special casing for (u)pTeX LaTeX formats
+ if
+ match(checkformat, "^latex$") and
+ match(engine, "^u?ptex$") then
+ realengine = "e" .. engine
+ end
+ -- Special casing for XeTeX engine
+ local checkopts = checkopts
+ if match(engine, "xetex") and not makepdf then
+ checkopts = checkopts .. " -no-pdf"
+ end
+ -- Special casing for ConTeXt
+ if match(checkformat, "^context$") then
+ format = ""
+ if engine == "luatex" or engine == "luajittex" then
+ realengine = "context"
+ elseif engine == "pdftex" then
+ realengine = "texexec"
+ elseif engine == "xetex" then
+ realengine = "texexec --xetex"
+ else
+ print("Engine incompatible with format")
+ exit(1)
+ end
+ end
+ local logfile = testdir .. "/" .. name .. logext
+ local newfile = testdir .. "/" .. name .. "." .. engine .. logext
+ local asciiopt = ""
+ for _,i in ipairs(asciiengines) do
+ if realengine == i then
+ asciiopt = "-translate-file ./ascii.tcx "
+ break
+ end
+ end
+ local errlevels = {}
+ for i = 1, checkruns do
+ errlevels[i] = run(
+ testdir,
+ -- No use of localdir here as the files get copied to testdir:
+ -- avoids any paths in the logs
+ os_setenv .. " TEXINPUTS=." .. (checksearch and os_pathsep or "")
+ .. os_concat ..
+ -- Avoid spurious output from (u)pTeX
+ os_setenv .. " GUESS_INPUT_KANJI_ENCODING=0"
+ .. os_concat ..
+ (forcecheckepoch and setepoch() or "") ..
+ -- Ensure lines are of a known length
+ os_setenv .. " max_print_line=" .. maxprintline
+ .. os_concat ..
+ realengine .. format .. " -jobname=" .. name .. " "
+ .. asciiopt .. " " .. checkopts .. " \"\\input " .. lvtfile .. "\" "
+ .. (hide and (" > " .. os_null) or "")
+ .. os_concat ..
+ runtest_tasks(jobname(lvtfile))
+ )
+ end
+ if makepdf and fileexists(testdir .. "/" .. name .. dviext) then
+ dvitopdf(name, testdir, engine, hide)
+ end
+ formatlog(logfile, newfile, engine, errlevels)
+ -- Store secondary files for this engine
+ for _,filetype in pairs(auxfiles) do
+ for _,file in pairs(filelist(testdir, filetype)) do
+ if match(file,"^" .. name .. ".[^.]+$") then
+ local ext = match(file, "%.[^.]+$")
+ if ext ~= lvtext and
+ ext ~= tlgext and
+ ext ~= lveext and
+ ext ~= logext then
+ ren(testdir, file, gsub(file, "(%.[^.]+)$", "." .. engine .. "%1"))
+ end
+ end
+ end
+ end
+end
+
+-- A hook to allow additional tasks to run for the tests
+runtest_tasks = runtest_tasks or function(name)
+ return ""
+end
+
+-- Look for a test: could be in the testfiledir or the unpackdir
+function testexists(test)
+ return(locate({testfiledir, unpackdir}, {test .. lvtext}))
+end
+
+-- Standard versions of the main targets for building modules
+
+function check(names)
+ local errorlevel = 0
+ if testfiledir ~= "" and direxists(testfiledir) then
+ if not options["rerun"] then
+ checkinit()
+ end
+ local hide = true
+ if names and next(names) then
+ hide = false
+ end
+ names = names or { }
+ -- No names passed: find all test files
+ if not next(names) then
+ for _,i in pairs(filelist(testfiledir, "*" .. lvtext)) do
+ insert(names, jobname(i))
+ end
+ for _,i in ipairs(filelist(unpackdir, "*" .. lvtext)) do
+ if fileexists(testfiledir .. "/" .. i) then
+ print("Duplicate test file: " .. i)
+ return 1
+ else
+ insert(names, jobname(i))
+ end
+ end
+ sort(names)
+ -- Deal limiting range of names
+ if options["first"] then
+ local allnames = names
+ local active = false
+ local firstname = options["first"]
+ names = { }
+ for _,name in ipairs(allnames) do
+ if name == firstname then
+ active = true
+ end
+ if active then
+ insert(names,name)
+ end
+ end
+ end
+ if options["last"] then
+ local allnames = names
+ local lastname = options["last"]
+ names = { }
+ for _,name in ipairs(allnames) do
+ insert(names,name)
+ if name == lastname then
+ break
+ end
+ end
+ end
+ end
+ -- https://stackoverflow.com/a/32167188
+ local function shuffle(tbl)
+ local len, random = #tbl, rnd
+ for i = len, 2, -1 do
+ local j = random(1, i)
+ tbl[i], tbl[j] = tbl[j], tbl[i]
+ end
+ return tbl
+ end
+ if options["shuffle"] then
+ names = shuffle(names)
+ end
+ -- Actually run the tests
+ print("Running checks on")
+ local i = 0
+ for _,name in ipairs(names) do
+ i = i + 1
+ print(" " .. name .. " (" .. i.. "/" .. #names ..")")
+ local errlevel = runcheck(name, hide)
+ -- Return value must be 1 not errlevel
+ if errlevel ~= 0 then
+ if options["halt-on-error"] then
+ return 1
+ else
+ errorlevel = 1
+ -- visually show that something has failed
+ print(" --> failed\n")
+ end
+ end
+ end
+ if errorlevel ~= 0 then
+ checkdiff()
+ else
+ print("\n All checks passed\n")
+ end
+ end
+ return errorlevel
+end
+
+-- A short auxiliary to print the list of differences for check
+function checkdiff()
+ print("\n Check failed with difference files")
+ for _,i in ipairs(filelist(testdir, "*" .. os_diffext)) do
+ print(" - " .. testdir .. "/" .. i)
+ end
+ for _,i in ipairs(filelist(testdir, "*" .. os_cmpext)) do
+ print(" - " .. testdir .. "/" .. i)
+ end
+ print("")
+end
+
+function showfaileddiff()
+ print("\nCheck failed with difference file")
+ for _,i in ipairs(filelist(testdir, "*" .. os_diffext)) do
+ print(" - " .. testdir .. "/" .. i)
+ print("")
+ local f = open(testdir .. "/" .. i,"r")
+ local content = f:read("*all")
+ close(f)
+ print("-----------------------------------------------------------------------------------")
+ print(content)
+ print("-----------------------------------------------------------------------------------")
+ end
+ for _,i in ipairs(filelist(testdir, "*" .. os_cmpext)) do
+ print(" - " .. testdir .. "/" .. i)
+ end
+end
+
+function save(names)
+ checkinit()
+ local engines = options["engine"] or {stdengine}
+ for _,name in pairs(names) do
+ local engine
+ for _,engine in pairs(engines) do
+ local tlgengine = ((engine == stdengine and "") or "." .. engine)
+ local tlgfile = name .. tlgengine .. tlgext
+ local spdffile = name .. tlgengine .. pdfext
+ local newfile = name .. "." .. engine .. logext
+ local pdffile = name .. "." .. engine .. pdfext
+ local refext = ((options["pdf"] and pdfext) or tlgext)
+ if testexists(name) then
+ print("Creating and copying " .. refext)
+ runtest(name, engine, false, lvtext, options["pdf"])
+ if options["pdf"] then
+ ren(testdir, pdffile, spdffile)
+ cp(spdffile, testdir, testfiledir)
+ else
+ ren(testdir, newfile, tlgfile)
+ cp(tlgfile, testdir, testfiledir)
+ end
+ if fileexists(unpackdir .. "/" .. tlgfile) then
+ print(
+ "Saved " .. tlgext
+ .. " file overrides unpacked version of the same name"
+ )
+ end
+ elseif locate({unpackdir, testfiledir}, {name .. lveext}) then
+ print(
+ "Saved " .. tlgext .. " file overrides a "
+ .. lveext .. " file of the same name"
+ )
+ else
+ print(
+ "Test input \"" .. testfiledir .. "/" .. name .. lvtext
+ .. "\" not found"
+ )
+ end
+ end
+ end
+end
+