tex-core add tag
samcarter
Thanks to [Phelype Oleinik](https://topanswers.xyz/transcript?room=347&id=97960#c97960) I learned to use `\loggingoutput` to produce logs which contain the output stream.

For example one can use `pdflatex "\loggingoutput\input{document}"` to compile the following example to produce two different .log files by comment/uncommenting the one line
```
\documentclass{beamer}

\usetheme{Madrid}

\setbeamertemplate{page number in head/foot}{}% comment or uncomment

\begin{document}

\begin{frame}
\end{frame}

\end{document}
```

Using `diff` on the .log files produced by this already works and shows the differences in the output stream (but also a lot of false positives about the different time of compilation, etc.)

Now I'm wondering how to use l3build to compare these log files?

Based on https://github.com/latex3/l3build/tree/main/examples/Simple-Flat I created the following `build.lua` file:

```
#!/usr/bin/env texlua
module = "document"
typesetfiles  = {"*.tex"}
```

How can I now configure l3build to compile the document with `pdflatex "\loggingoutput\input{document}"` and compare the .log file?
Top Answer
Phelype Oleinik
Here's a basic setup.  I have this directory structure:
```none
samcarter/
├── build.lua
└── testfiles/
    ├── test-001.lvt
    └── test-001.tlg     <-- added later
```

In the base folder, beside `build.lua`, you'd have for example the source code for the package/class you are working on.  Inside `testfiles` you start with as many `.lvt` test files for your tests (we'll deal with the `.tlg` later: they are the log output of the tests).

Now you start with a basic `build.lua`:
```lua
#!/usr/bin/env texlua
module = "document"
checkruns    = 2
checkengines = { "pdftex" }
```
The `checkruns` line tells `l3build` to run each test file twice before comparing (for example if you have cross-references to resolve in the test file), and the `checkengines` line tells which engine to use.  Change these variables to suit your needs.

Now when running LaTeX, the `.log` usually contains a lot of unwanted noise, so by default `l3build` only tests what you tell it to.  You do that by loading the `regression-test.tex` file, and telling it to `\START` the test, and then later (optionally) `\END` it. If `\END` is not there, it goes until the `\end{document}` as usual.  You can also omit parts of the log by using `\OMIT`, and then `\TIMO` to resume logging.  Your test file `test-001.lvt` would look like this:
```
\input regression-test.tex
\loggingoutput \START % everything from here on will be tested
\documentclass{beamer}
\usetheme{Madrid}
\setbeamertemplate{page number in head/foot}{}% comment or uncomment
\begin{document}
\begin{frame}
\end{frame}
\end{document}
```
(I'll get to the part that you don't want to change the test file later.)

Now you open the terminal in the `samcarter` folder and run
```sh
l3build save test-001
```
then `l3build` will run `test-001.lvt`, and save the requested parts of the `.log` into `testfiles/test-001.tlg`.  Later, when you ~~break~~ change `beamer` you can run
```sh
l3build check
```
to run all the test suite (or `l3build check test-001` to run a single test).  If you save the test file above, and then comment out the `\setbeamertemplate` line and run `l3build check`, the terminal will say:
```none
Running checks on
  test-001 (1/1)
          --> failed

  Check failed with difference files
  - ./build/test/test-001.pdftex.diff
```
and if you open the `.diff` file you will see:
```diff
*** ./build/test/test1.tlg	2021-05-14 20:32:45.584923163 -0300
--- ./build/test/test1.pdftex.log	2021-05-14 20:32:47.368894809 -0300
***************
*** 1244,1251 ****
  ..............\glue 0.0
  ..............\hbox(5.99997+2.66666)x121.39659
  ...............\pdfcolorstack 0 push {1 g 1 G}
! ...............\vbox(4.16666+1.16666)x121.39659
! ................\hbox(4.16666+1.16666)x121.39659, glue set 64.65634fill
  .................\glue(\leftskip) 0.0 plus 1.0fill
  .................\hbox(0.0+0.0)x0.0
  .................\hbox(4.16666+1.16666)x36.53172
--- 1244,1251 ----
  ..............\glue 0.0
  ..............\hbox(5.99997+2.66666)x121.39659
  ...............\pdfcolorstack 0 push {1 g 1 G}
! ...............\vbox(4.5+1.5)x121.39659
! ................\hbox(4.5+1.5)x121.39659, glue set 52.96825fill
  .................\glue(\leftskip) 0.0 plus 1.0fill
  .................\hbox(0.0+0.0)x0.0
  .................\hbox(4.16666+1.16666)x36.53172
***************
*** 1268,1273 ****
--- 1268,1278 ----
  .................\glue 0.0
  .................\glue 2.12503 plus 1.06252 minus 0.70834
  .................\pdfcolorstack 0 push {1 g 1 G}
+ .................\OT1/cmss/m/n/6 1
+ .................\kern 1.06273
+ .................\OT1/cmss/m/n/6 /
+ .................\kern 1.06273
+ .................\OT1/cmss/m/n/6 1
  .................\rule(*+*)x0.0
  .................\penalty 10000
  .................\glue 5.33331
```

---

Now about not changing the test files: that's tough :-|

From what I could understand in the `l3build` code, it cannot natively add arbitrary commands before the test file is input (it could be an useful feature), so I hacked into `l3build-check.lua` to add that for you: the `build.lua` file below adds an `extracmds` variable that you can set to, in your case:
```lua
extracmds = '\\input regression-test.tex \\loggingoutput \\START'
```
These commands will be executed directly before the `.lvt` file is read in.

I'd suggest you add the extra commands directly into the test file, so you have finer control of what is tested (for example by `\OMIT`ting package loading, which changes from one system to another and makes automated testing a nightmare :)), but the hack below should work (until that part of `l3build` changes).
```lua
#!/usr/bin/env texlua
module = "document"
checkruns    = 2
checkengines = { "pdftex" }
extracmds    = '\\input regression-test.tex \\loggingoutput \\START'

-- Hack to add extracmds
-- copy of runtest from l3build-check.lua, except for the marked lines
function runtest(name, engine, hide, ext, test_type, breakout)
  local lvtfile = name .. (ext or lvtext)
  cp(lvtfile, fileexists(testfiledir .. "/" .. lvtfile)
    and testfiledir or unpackdir, testdir)
  local checkopts = checkopts
  engine = engine or stdengine
  local binary = engine
  local format = string.gsub(engine,"tex$",checkformat)
  -- Special binary/format combos
  local special_check = specialformats[checkformat]
  if special_check and next(special_check) then
    local engine_info = special_check[engine]
    if engine_info then
      binary    = engine_info.binary  or binary
      format    = engine_info.format  or format
      checkopts = engine_info.options or checkopts
    end
  end
  -- Finalise format string
  if format ~= "" then
    format = " --fmt=" .. format
  end
  -- Special casing for XeTeX engine
  if string.match(engine, "xetex") and test_type.generated ~= pdfext then
    checkopts = checkopts .. " -no-pdf"
  end
  -- Special casing for ConTeXt
  local function setup(file)
    extracmds = extracmds or "" -- <<< Added extracmds here
    return " -jobname=" .. name .. " " .. ' "' .. extracmds .. '\\input ' .. file .. '" ' -- <<< Added extracmds here
  end
  if string.match(checkformat,"^context$") then
    function setup(file) return ' "' .. file .. '" '  end
  end
  local basename = testdir .. "/" .. name
  local gen_file = basename .. test_type.generated
  local new_file = basename .. "." .. engine .. test_type.generated
  local asciiopt = ""
  for _,i in ipairs(asciiengines) do
    if binary == i then
      asciiopt = "-translate-file ./ascii.tcx "
      break
    end
  end
  -- Clean out any dynamic files
  for _,filetype in pairs(dynamicfiles) do
    rm(testdir,filetype)
  end
  -- Ensure there is no stray .log file
  rmfile(testdir,name .. logext)
  local errlevels = {}
  local localtexmf = ""
  if texmfdir and texmfdir ~= "" and direxists(texmfdir) then
    localtexmf = os_pathsep .. abspath(texmfdir) .. "//"
  end
  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=." .. localtexmf
        .. (checksearch and os_pathsep or "")
        .. os_concat ..
      os_setenv .. " LUAINPUTS=." .. localtexmf
        .. (checksearch and os_pathsep or "")
        .. os_concat ..
      -- Avoid spurious output from (u)pTeX
      os_setenv .. " GUESS_INPUT_KANJI_ENCODING=0"
        .. os_concat ..
      -- Allow for local texmf files
      os_setenv .. " TEXMFCNF=." .. os_pathsep
        .. os_concat ..
      set_epoch_cmd(epoch, forcecheckepoch) ..
      -- Ensure lines are of a known length
      os_setenv .. " max_print_line=" .. maxprintline
        .. os_concat ..
      binary .. format
        .. " " .. asciiopt .. " " .. checkopts
        .. setup(lvtfile)
        .. (hide and (" > " .. os_null) or "")
        .. os_concat ..
      runtest_tasks(jobname(lvtfile),i)
    )
    -- Break the loop if the result is stable
    if breakout and i < checkruns then
      if test_type.generated == pdfext then
        if fileexists(testdir .. "/" .. name .. dviext) then
          dvitopdf(name, testdir, engine, hide)
        end
      end
      test_type.rewrite(gen_file,new_file,engine,errlevels)
      if base_compare(test_type,name,engine,true) == 0 then
        break
      end
    end
  end
  if test_type.generated == pdfext then
    if fileexists(testdir .. "/" .. name .. dviext) then
      dvitopdf(name, testdir, engine, hide)
    end
    cp(name .. pdfext,testdir,resultdir)
    ren(resultdir,name .. pdfext,name .. "." .. engine .. pdfext)
  end
  test_type.rewrite(gen_file,new_file,engine,errlevels)
  -- Store secondary files for this engine
  for _,filetype in pairs(auxfiles) do
    for _,file in pairs(filelist(testdir, filetype)) do
      if string.match(file,"^" .. name .. "%.[^.]+$") then
        local newname = string.gsub(file,"(%.[^.]+)$","." .. engine .. "%1")
        if fileexists(testdir .. "/" .. newname) then
          rmfile(testdir,newname)
        end
        ren(testdir,file,newname)
      end
    end
  end
  return 0
end
```

Enter question or answer id or url (and optionally further answer ids/urls from the same question) from

Separate each id/url with a space. No need to list your own answers; they will be imported automatically.