lune/scripts/generate_compression_test_files.luau

304 lines
9 KiB
Lua
Raw Normal View History

2024-05-12 12:30:32 +01:00
local fs = require("@lune/fs")
local process = require("@lune/process")
local serde = require("@lune/serde")
local stdio = require("@lune/stdio")
local TEST_FILES_DIR = process.cwd .. "tests/serde/test-files"
local INPUT_FILE = TEST_FILES_DIR .. "/loremipsum.txt"
local TEMP_FILE = TEST_FILES_DIR .. "/loremipsum.temp"
local INPUT_FILE_CONTENTS = fs.readFile(INPUT_FILE)
-- Make some utility functions for viewing unexpected differences in files easier
local function stringAsHex(str: string): string
local hex = {}
for i = 1, #str do
table.insert(hex, string.format("%02x", string.byte(str, i)))
end
return table.concat(hex)
end
local function hexDiff(a: string, b: string): string
local diff = {}
for i = 1, math.max(#a, #b) do
local aByte = if #a >= i then string.byte(a, i) else nil
local bByte = if #b >= i then string.byte(b, i) else nil
if aByte == nil then
table.insert(
diff,
string.format(
"%s%02x%s",
stdio.color("green"),
assert(bByte, "unreachable"),
stdio.color("reset")
)
)
elseif bByte == nil then
table.insert(
diff,
string.format(
"%s%02x%s",
stdio.color("red"),
assert(aByte, "unreachable"),
stdio.color("reset")
)
)
else
if aByte == bByte then
table.insert(diff, string.format("%02x", aByte))
else
table.insert(
diff,
string.format(
"%s%02x%s",
stdio.color("yellow"),
assert(bByte, "unreachable"),
stdio.color("reset")
)
)
end
end
end
return table.concat(diff)
end
local function stripCwdIfPresent(path: string): string
if string.sub(path, 1, #process.cwd) == process.cwd then
return string.sub(path, #process.cwd + 1)
else
return path
end
end
-- Make some processing functions for manipulating output of certain commands
local function processNoop(output: string): string
return output
end
local function processGzipSetOsUnknown(output: string): string
-- This will set the os bits to be "unknown" so that the
-- output is deterministic and consistent with serde lib
-- https://www.rfc-editor.org/rfc/rfc1952#section-2.3.1
local buf = buffer.fromstring(output)
buffer.writeu8(buf, 9, 0xFF)
return buffer.tostring(buf)
end
local function processLz4PrependSize(output: string): string
-- Lune supports only lz4 with the decompressed size
-- prepended to it, but the lz4 command line tool
-- doesn't add this automatically, so we have to
-- TODO: Remove this in the future when no longer needed
local buf = buffer.create(4 + #output)
buffer.writeu32(buf, 0, #INPUT_FILE_CONTENTS)
buffer.writestring(buf, 4, output)
return buffer.tostring(buf)
end
-- Make sure we have all of the different compression tools installed,
-- note that on macos we do not use the system-installed compression
-- tools, instead preferring to use homebrew-installed (gnu) ones
local BIN_BROTLI = if process.os == "macos" then "/opt/homebrew/bin/brotli" else "brotli"
local BIN_GZIP = if process.os == "macos" then "/opt/homebrew/bin/gzip" else "gzip"
local BIN_LZ4 = if process.os == "macos" then "/opt/homebrew/bin/lz4" else "lz4"
local BIN_ZLIB = if process.os == "macos" then "/opt/homebrew/bin/pigz" else "pigz"
local function checkInstalled(program: string, args: { string }?)
print("Checking if", program, "is installed")
local result = process.spawn(program, args)
if not result.ok then
stdio.ewrite(string.format("Program '%s' is not installed\n", program))
process.exit(1)
end
end
checkInstalled(BIN_BROTLI, { "--version" })
checkInstalled(BIN_GZIP, { "--version" })
checkInstalled(BIN_LZ4, { "--version" })
checkInstalled(BIN_ZLIB, { "--version" })
-- Run them to generate files
local function run(program: string, args: { string }): string
local result = process.spawn(program, args)
if not result.ok then
stdio.ewrite(string.format("Command '%s' failed\n", program))
if #result.stdout > 0 then
stdio.ewrite("stdout:\n")
stdio.ewrite(result.stdout)
stdio.ewrite("\n")
end
if #result.stderr > 0 then
stdio.ewrite("stderr:\n")
stdio.ewrite(result.stderr)
stdio.ewrite("\n")
end
process.exit(1)
else
if #result.stdout > 0 then
stdio.ewrite("stdout:\n")
stdio.ewrite(result.stdout)
stdio.ewrite("\n")
end
end
return result.stdout
end
local OUTPUT_FILES = {
{
command = BIN_BROTLI,
format = "brotli" :: serde.CompressDecompressFormat,
args = { "--best", "-w", "22", TEMP_FILE },
output = TEMP_FILE .. ".br",
process = processNoop,
final = INPUT_FILE .. ".br",
},
{
command = BIN_GZIP,
format = "gzip" :: serde.CompressDecompressFormat,
args = { "--best", "--no-name", "--synchronous", TEMP_FILE },
output = TEMP_FILE .. ".gz",
process = processGzipSetOsUnknown,
final = INPUT_FILE .. ".gz",
},
{
command = BIN_LZ4,
format = "lz4" :: serde.CompressDecompressFormat,
args = { "--best", TEMP_FILE, TEMP_FILE .. ".lz4" },
output = TEMP_FILE .. ".lz4",
process = processLz4PrependSize,
final = INPUT_FILE .. ".lz4",
},
{
command = BIN_ZLIB,
format = "zlib" :: serde.CompressDecompressFormat,
args = { "--best", "--zlib", TEMP_FILE },
output = TEMP_FILE .. ".zz",
process = processNoop,
final = INPUT_FILE .. ".z",
},
}
for _, spec in OUTPUT_FILES do
-- Write the temp file for the compression tool to read and use, then
-- remove it, some tools may remove it on their own, so we ignore errors
fs.writeFile(TEMP_FILE, INPUT_FILE_CONTENTS)
local argsToDisplay = {}
for _, arg in spec.args do
table.insert(argsToDisplay, stripCwdIfPresent(arg))
end
print(
"\nRunning compression\n Cmd: ",
spec.command,
"\n Args: ",
stdio.format(table.unpack(argsToDisplay))
)
local output = run(spec.command, spec.args)
if #output > 0 then
print("Output:", output)
end
pcall(fs.removeFile, TEMP_FILE)
-- Read the compressed output file that is now supposed to exist
local compressedContents
pcall(function()
compressedContents = fs.readFile(spec.output)
compressedContents = spec.process(compressedContents)
fs.removeFile(spec.output)
end)
if not compressedContents then
error(
string.format(
"Nothing was written to output file while running %s:\n%s",
spec.command,
spec.output
)
)
end
-- If the newly compressed contents do not match the existing contents,
-- warn the user about this and ask if they want to overwrite the file
local existingContents = fs.readFile(spec.final)
if compressedContents ~= existingContents then
stdio.ewrite("\nCompressed file does not match existing contents!")
stdio.ewrite("\n\nExisting:\n")
stdio.ewrite(stringAsHex(existingContents))
stdio.ewrite("\n\nCompressed:\n")
stdio.ewrite(hexDiff(existingContents, compressedContents))
stdio.ewrite("\n\n")
local confirm = stdio.prompt("confirm", "Do you want to continue?")
if confirm == true then
print("Overwriting file!")
else
stdio.ewrite("\n\nAborting...\n")
process.exit(1)
return
end
end
-- Check if the compressed contents can be decompressed using serde
local decompressSuccess, decompressedContents =
pcall(serde.decompress, spec.format, compressedContents)
if not decompressSuccess then
stdio.ewrite("\nCompressed contents could not be decompressed using serde!")
stdio.ewrite("\n\nCompressed:\n")
stdio.ewrite(stringAsHex(compressedContents))
stdio.ewrite("\n\nError:\n")
stdio.ewrite(tostring(decompressedContents))
stdio.ewrite("\n\n")
local confirm = stdio.prompt("confirm", "Do you want to continue?")
if confirm == true then
print("Ignoring decompression error!")
else
stdio.ewrite("\n\nAborting...\n")
process.exit(1)
return
end
end
if decompressedContents ~= INPUT_FILE_CONTENTS then
stdio.ewrite("\nCompressed contents were not decompressable properly using serde!")
stdio.ewrite("\n\nOriginal:\n")
stdio.ewrite(INPUT_FILE_CONTENTS)
stdio.ewrite("\n\nDecompressed:\n")
stdio.ewrite(decompressedContents)
stdio.ewrite("\n\n")
local confirm = stdio.prompt("confirm", "Do you want to continue?")
if confirm == true then
print("Ignoring decompression mismatch!")
else
stdio.ewrite("\n\nAborting...\n")
process.exit(1)
return
end
end
-- Check if the compressed contents match the serde compressed contents,
-- if they don't this will 100% make the tests fail, but maybe we are doing
-- it because we are updating the serde library and need to update test files
local serdeContents = serde.compress(spec.format, INPUT_FILE_CONTENTS)
if compressedContents ~= serdeContents then
stdio.ewrite("\nTemp file does not match contents compressed with serde!")
stdio.ewrite("\nThis will caused the new compressed file to fail tests.")
stdio.ewrite("\n\nSerde:\n")
stdio.ewrite(stringAsHex(serdeContents))
stdio.ewrite("\n\nCompressed:\n")
stdio.ewrite(hexDiff(serdeContents, compressedContents))
stdio.ewrite("\n\n")
local confirm = stdio.prompt("confirm", "Do you want to continue?")
if confirm == true then
print("Writing new file!")
else
stdio.ewrite("\n\nAborting...\n")
process.exit(1)
return
end
end
-- Finally, write the new compressed file
fs.writeFile(spec.final, compressedContents)
print("Wrote new file successfully to", stripCwdIfPresent(spec.final))
end