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
```