style: apply stylua formatter

This commit is contained in:
Erica Marigold 2025-01-08 14:14:30 +00:00
parent 9d3c815fbb
commit 03320fe090
Signed by: DevComp
GPG key ID: 429EF1C337871656
9 changed files with 489 additions and 485 deletions

View file

@ -5,7 +5,7 @@ local asciitable = require("../luau_packages/asciitable")
local file = fs.readFile("tests/data/files_and_dirs.zip") local file = fs.readFile("tests/data/files_and_dirs.zip")
local reader = zip.load(buffer.fromstring(file)) local reader = zip.load(buffer.fromstring(file))
--- Transforms a tree of recursive `{ [string]: EntryData }` to a recursive tree of --- Transforms a tree of recursive `{ [string]: EntryData }` to a recursive tree of
--- `{ [string]: string }` --- `{ [string]: string }`
local function formatTree(tree: Tree): Tree<string> local function formatTree(tree: Tree): Tree<string>
local result: Tree<string> = {} local result: Tree<string> = {}

View file

@ -431,7 +431,6 @@ function ZipReader.extract(self: ZipReader, entry: ZipEntry, options: Extraction
skip = true, skip = true,
})) }))
-- Check if the path was a relative path -- Check if the path was a relative path
if path.isRelative(linkPath) then if path.isRelative(linkPath) then
if string.sub(linkPath, -1) ~= "/" then if string.sub(linkPath, -1) ~= "/" then

View file

@ -1,40 +1,40 @@
--- Canonicalize a path by removing redundant components --- Canonicalize a path by removing redundant components
local function canonicalize(path: string): string local function canonicalize(path: string): string
-- NOTE: It is fine for us to use `/` here because ZIP file names -- NOTE: It is fine for us to use `/` here because ZIP file names
-- always use `/` as the path separator -- always use `/` as the path separator
local components = string.split(path, "/") local components = string.split(path, "/")
local result = {} local result = {}
for _, component in components do for _, component in components do
if component == "." then if component == "." then
-- Skip current directory -- Skip current directory
continue continue
end end
if component == ".." then if component == ".." then
-- Traverse one upwards -- Traverse one upwards
table.remove(result, #result) table.remove(result, #result)
continue continue
end end
-- Otherwise, add the component to the result -- Otherwise, add the component to the result
table.insert(result, component) table.insert(result, component)
end end
return table.concat(result, "/") return table.concat(result, "/")
end end
--- Check if a path is absolute --- Check if a path is absolute
local function isAbsolute(path: string): boolean local function isAbsolute(path: string): boolean
return (string.match(path, "^/") or string.match(path, "^[a-zA-Z]:[\\/]") or string.match(path, "^//")) ~= nil return (string.match(path, "^/") or string.match(path, "^[a-zA-Z]:[\\/]") or string.match(path, "^//")) ~= nil
end end
--- Check if a path is relative --- Check if a path is relative
local function isRelative(path: string): boolean local function isRelative(path: string): boolean
return not isAbsolute(path) return not isAbsolute(path)
end end
return { return {
canonicalize = canonicalize, canonicalize = canonicalize,
isAbsolute = isAbsolute, isAbsolute = isAbsolute,
isRelative = isRelative, isRelative = isRelative,
} }

View file

@ -1,22 +1,22 @@
local crc32 = require("../crc") local crc32 = require("../crc")
export type CrcValidationOptions = { export type CrcValidationOptions = {
skip: boolean, skip: boolean,
expected: number, expected: number,
} }
local function validateCrc(decompressed: buffer, validation: CrcValidationOptions) local function validateCrc(decompressed: buffer, validation: CrcValidationOptions)
-- Unless skipping validation is requested, we verify the checksum -- Unless skipping validation is requested, we verify the checksum
if not validation.skip then if not validation.skip then
local computed = crc32(decompressed) local computed = crc32(decompressed)
assert( assert(
validation.expected == computed, validation.expected == computed,
`Validation failed; CRC checksum does not match: {string.format("%x", computed)} ~= {string.format( `Validation failed; CRC checksum does not match: {string.format("%x", computed)} ~= {string.format(
"%x", "%x",
computed computed
)} (expected ~= got)` )} (expected ~= got)`
) )
end end
end end
return validateCrc return validateCrc

View file

@ -1,40 +1,41 @@
local fs = require("@lune/fs") local fs = require("@lune/fs")
local process = require("@lune/process") local process = require("@lune/process")
local serde = require("@lune/serde") local serde = require("@lune/serde")
local frktest = require("../lune_packages/frktest") local frktest = require("../lune_packages/frktest")
local check = frktest.assert.check local check = frktest.assert.check
local ZipReader = require("../lib") local ZipReader = require("../lib")
return function(test: typeof(frktest.test)) return function(test: typeof(frktest.test))
test.suite("Edge case tests", function() test.suite("Edge case tests", function()
test.case("Handles misaligned comment properly", function() test.case("Handles misaligned comment properly", function()
local data = fs.readFile("tests/data/misaligned_comment.zip") local data = fs.readFile("tests/data/misaligned_comment.zip")
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
check.equal(zip.comment, "short.") check.equal(zip.comment, "short.")
end) end)
test.case("Follows symlinks correctly", function() test.case("Follows symlinks correctly", function()
-- TODO: More test files with symlinks -- TODO: More test files with symlinks
local data = fs.readFile("tests/data/pandoc_soft_links.zip") local data = fs.readFile("tests/data/pandoc_soft_links.zip")
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
local entry = assert(zip:findEntry("/pandoc-3.2-arm64/bin/pandoc-lua")) local entry = assert(zip:findEntry("/pandoc-3.2-arm64/bin/pandoc-lua"))
assert(entry:isSymlink(), "Entry type must be a symlink") assert(entry:isSymlink(), "Entry type must be a symlink")
local targetPath = zip:extract(entry, { isString = true }) :: string local targetPath = zip:extract(entry, { isString = true }) :: string
check.equal(targetPath, "pandoc") check.equal(targetPath, "pandoc")
local bin = zip:extract(entry, { isString = false, followSymlinks = true }) :: buffer local bin = zip:extract(entry, { isString = false, followSymlinks = true }) :: buffer
local expectedBin = process.spawn("unzip", { "-p", "tests/data/pandoc_soft_links.zip", "pandoc-3.2-arm64/bin/pandoc" }) local expectedBin =
check.is_true(expectedBin.ok) process.spawn("unzip", { "-p", "tests/data/pandoc_soft_links.zip", "pandoc-3.2-arm64/bin/pandoc" })
check.is_true(expectedBin.ok)
-- Compare hashes instead of the entire binary to improve speed and not print out
-- the entire binary data in case there's a mismatch -- Compare hashes instead of the entire binary to improve speed and not print out
check.equal(serde.hash("blake3", bin), serde.hash("blake3", expectedBin.stdout)) -- the entire binary data in case there's a mismatch
end) check.equal(serde.hash("blake3", bin), serde.hash("blake3", expectedBin.stdout))
end) end)
end end)
end

View file

@ -1,79 +1,79 @@
local fs = require("@lune/fs") local fs = require("@lune/fs")
local process = require("@lune/process") local process = require("@lune/process")
local frktest = require("../lune_packages/frktest") local frktest = require("../lune_packages/frktest")
local check = frktest.assert.check local check = frktest.assert.check
local ZipReader = require("../lib") local ZipReader = require("../lib")
-- Reuse the same ZIP files from metadata tests -- Reuse the same ZIP files from metadata tests
local ZIPS = fs.readDir("tests/data") local ZIPS = fs.readDir("tests/data")
local FALLIBLES = { local FALLIBLES = {
"invalid_cde_number_of_files_allocation_greater_offset.zip", "invalid_cde_number_of_files_allocation_greater_offset.zip",
-- FIXME: Incorrectly handled, file tree is empty and walk silently errors -- FIXME: Incorrectly handled, file tree is empty and walk silently errors
-- "invalid_cde_number_of_files_allocation_smaller_offset.zip", -- "invalid_cde_number_of_files_allocation_smaller_offset.zip",
"invalid_offset.zip", "invalid_offset.zip",
"invalid_offset2.zip", "invalid_offset2.zip",
-- FIXME: Does not error when it should -- FIXME: Does not error when it should
-- "comment_garbage.zip", -- "comment_garbage.zip",
"chinese.zip", "chinese.zip",
"non_utf8.zip", -- FIXME: Lune breaks for non utf8 data in process stdout "non_utf8.zip", -- FIXME: Lune breaks for non utf8 data in process stdout
"pandoc_soft_links.zip", -- Soft links are tested separately in edge_cases "pandoc_soft_links.zip", -- Soft links are tested separately in edge_cases
} }
return function(test: typeof(frktest.test)) return function(test: typeof(frktest.test))
test.suite("ZIP extraction tests", function() test.suite("ZIP extraction tests", function()
for _, file in ZIPS do for _, file in ZIPS do
if not string.match(file, "%.zip$") then if not string.match(file, "%.zip$") then
continue continue
end end
local checkErr: ((...any) -> any?) -> nil = if table.find(FALLIBLES, file) local checkErr: ((...any) -> any?) -> nil = if table.find(FALLIBLES, file)
then check.should_error then check.should_error
else check.should_not_error else check.should_not_error
test.case(`Extracts files correctly - {file}`, function() test.case(`Extracts files correctly - {file}`, function()
checkErr(function() checkErr(function()
local zipPath = "tests/data/" .. file local zipPath = "tests/data/" .. file
local data = fs.readFile(zipPath) local data = fs.readFile(zipPath)
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
-- Test both string and buffer extraction -- Test both string and buffer extraction
local stringOptions = { isString = true, decompress = true } local stringOptions = { isString = true, decompress = true }
local bufferOptions = { isString = false, decompress = true } local bufferOptions = { isString = false, decompress = true }
-- Extract and verify each file -- Extract and verify each file
zip:walk(function(entry) zip:walk(function(entry)
if entry.isDirectory then if entry.isDirectory then
return return
end end
-- Extract with unzip for comparison -- Extract with unzip for comparison
local unzipOutput = process.spawn(`unzip`, { "-p", zipPath, entry:getPath() }) local unzipOutput = process.spawn(`unzip`, { "-p", zipPath, entry:getPath() })
-- NOTE: We use assert since we don't know whether to expect true or false -- NOTE: We use assert since we don't know whether to expect true or false
assert(unzipOutput.ok) assert(unzipOutput.ok)
-- Test string extraction -- Test string extraction
local contentString = zip:extract(entry, stringOptions) :: string local contentString = zip:extract(entry, stringOptions) :: string
check.equal(#contentString, entry.size) check.equal(#contentString, entry.size)
check.equal(contentString, unzipOutput.stdout) check.equal(contentString, unzipOutput.stdout)
-- Test buffer extraction -- Test buffer extraction
local contentBuffer = zip:extract(entry, bufferOptions) :: buffer local contentBuffer = zip:extract(entry, bufferOptions) :: buffer
check.equal(buffer.len(contentBuffer), entry.size) check.equal(buffer.len(contentBuffer), entry.size)
check.equal(buffer.tostring(contentBuffer), unzipOutput.stdout) check.equal(buffer.tostring(contentBuffer), unzipOutput.stdout)
-- Test directory extraction -- Test directory extraction
local parentPath = entry:getPath():match("(.+)/[^/]*$") or "/" local parentPath = entry:getPath():match("(.+)/[^/]*$") or "/"
local dirContents = zip:extractDirectory(parentPath, stringOptions) local dirContents = zip:extractDirectory(parentPath, stringOptions)
check.not_nil(dirContents[entry:getPath()]) check.not_nil(dirContents[entry:getPath()])
check.equal(dirContents[entry:getPath()], unzipOutput.stdout) check.equal(dirContents[entry:getPath()], unzipOutput.stdout)
end) end)
return return
end) end)
end) end)
end end
end) end)
end end

View file

@ -1,75 +1,75 @@
local fs = require("@lune/fs") local fs = require("@lune/fs")
local process = require("@lune/process") local process = require("@lune/process")
local frktest = require("../lune_packages/frktest") local frktest = require("../lune_packages/frktest")
local check = frktest.assert.check local check = frktest.assert.check
local ZipReader = require("../lib") local ZipReader = require("../lib")
return function(test: typeof(frktest.test)) return function(test: typeof(frktest.test))
test.suite("ZIP listing tests (top-level)", function() test.suite("ZIP listing tests (top-level)", function()
test.case("Lists all entries correctly", function() test.case("Lists all entries correctly", function()
-- Read our test zip file -- Read our test zip file
local data = fs.readFile("tests/data/files_and_dirs.zip") local data = fs.readFile("tests/data/files_and_dirs.zip")
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
-- Get listing from our implementation -- Get listing from our implementation
local entries = {} local entries = {}
for _, entry in zip:listDirectory("/") do for _, entry in zip:listDirectory("/") do
table.insert(entries, entry:getPath()) table.insert(entries, entry:getPath())
end end
-- Get listing from zip command -- Get listing from zip command
local result = process.spawn("zip", {"-sf", "tests/data/files_and_dirs.zip"}) local result = process.spawn("zip", { "-sf", "tests/data/files_and_dirs.zip" })
check.is_true(result.ok) check.is_true(result.ok)
local zipOutput = result.stdout local zipOutput = result.stdout
-- Parse zip command output into sorted array -- Parse zip command output into sorted array
local zipEntries = {} local zipEntries = {}
for line in string.gmatch(zipOutput, "[^\r\n]+") do for line in string.gmatch(zipOutput, "[^\r\n]+") do
-- Skip header/footer lines -- Skip header/footer lines
if not string.match(line, "^Archive contains:") and not string.match(line, "^Total %d+ entries") then if not string.match(line, "^Archive contains:") and not string.match(line, "^Total %d+ entries") then
table.insert(zipEntries, string.match(line, "^%s*(.+)$")) table.insert(zipEntries, string.match(line, "^%s*(.+)$"))
end end
end end
-- Compare results -- Compare results
for _, entry in entries do for _, entry in entries do
check.not_nil(table.find(zipEntries, entry)) check.not_nil(table.find(zipEntries, entry))
end end
end) end)
test.case("Lists directories correctly", function() test.case("Lists directories correctly", function()
local data = fs.readFile("tests/data/files_and_dirs.zip") local data = fs.readFile("tests/data/files_and_dirs.zip")
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
local dirs = {} local dirs = {}
for _, entry in zip:listDirectory("/") do for _, entry in zip:listDirectory("/") do
if entry.isDirectory then if entry.isDirectory then
table.insert(dirs, entry:getPath()) table.insert(dirs, entry:getPath())
end end
end end
-- Verify all directory paths end with / -- Verify all directory paths end with /
for _, dir in dirs do for _, dir in dirs do
check.equal(string.sub(dir, -1), "/") check.equal(string.sub(dir, -1), "/")
end end
end) end)
test.case("Directory statistics match", function() test.case("Directory statistics match", function()
local data = fs.readFile("tests/data/files_and_dirs.zip") local data = fs.readFile("tests/data/files_and_dirs.zip")
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
local stats = zip:getStats() local stats = zip:getStats()
-- Get file count from zip command -- Get file count from zip command
local result = process.spawn("zip", {"-sf", "tests/data/files_and_dirs.zip"}) local result = process.spawn("zip", { "-sf", "tests/data/files_and_dirs.zip" })
check.is_true(result.ok) check.is_true(result.ok)
-- Parse file count from last line of zip output -- Parse file count from last line of zip output
local fileCount = tonumber(string.match(result.stdout, "Total (%d+) entries.*$")) local fileCount = tonumber(string.match(result.stdout, "Total (%d+) entries.*$"))
check.equal(stats.fileCount + stats.dirCount, fileCount) check.equal(stats.fileCount + stats.dirCount, fileCount)
end) end)
end) end)
end end

View file

@ -1,139 +1,143 @@
local fs = require("@lune/fs") local fs = require("@lune/fs")
local process = require("@lune/process") local process = require("@lune/process")
local DateTime = require("@lune/datetime") local DateTime = require("@lune/datetime")
local frktest = require("../lune_packages/frktest") local frktest = require("../lune_packages/frktest")
local check = frktest.assert.check local check = frktest.assert.check
local unzip = require("../lib") local unzip = require("../lib")
local ZIPS = fs.readDir("tests/data") local ZIPS = fs.readDir("tests/data")
local FALLIBLES = { local FALLIBLES = {
"invalid_cde_number_of_files_allocation_greater_offset.zip", "invalid_cde_number_of_files_allocation_greater_offset.zip",
"invalid_cde_number_of_files_allocation_smaller_offset.zip", "invalid_cde_number_of_files_allocation_smaller_offset.zip",
"invalid_offset.zip", "invalid_offset.zip",
"invalid_offset2.zip", "invalid_offset2.zip",
"misaligned_comment.zip", "misaligned_comment.zip",
"comment_garbage.zip", "comment_garbage.zip",
"chinese.zip" -- FIXME: Support encoding other than UTF-8 and ASCII using OS APIs after FFI "chinese.zip", -- FIXME: Support encoding other than UTF-8 and ASCII using OS APIs after FFI
} }
local METHOD_NAME_TRANSFORMATIONS: { [string]: unzip.CompressionMethod } = { local METHOD_NAME_TRANSFORMATIONS: { [string]: unzip.CompressionMethod } = {
["Defl:N"] = "DEFLATE", ["Defl:N"] = "DEFLATE",
["Stored"] = "STORE", ["Stored"] = "STORE",
} }
local function timestampToValues(dosTimestamp: number): DateTime.DateTimeValues local function timestampToValues(dosTimestamp: number): DateTime.DateTimeValues
local time = bit32.band(dosTimestamp, 0xFFFF) local time = bit32.band(dosTimestamp, 0xFFFF)
local date = bit32.band(bit32.rshift(dosTimestamp, 16), 0xFFFF) local date = bit32.band(bit32.rshift(dosTimestamp, 16), 0xFFFF)
return { return {
year = bit32.band(bit32.rshift(date, 9), 0x7f) + 1980, year = bit32.band(bit32.rshift(date, 9), 0x7f) + 1980,
month = bit32.band(bit32.rshift(date, 5), 0x0f), month = bit32.band(bit32.rshift(date, 5), 0x0f),
day = bit32.band(date, 0x1f), day = bit32.band(date, 0x1f),
hour = bit32.band(bit32.rshift(time, 11), 0x1f), hour = bit32.band(bit32.rshift(time, 11), 0x1f),
minute = bit32.band(bit32.rshift(time, 5), 0x3f), minute = bit32.band(bit32.rshift(time, 5), 0x3f),
second = bit32.band(time, 0x1f) * 2, second = bit32.band(time, 0x1f) * 2,
} }
end end
function dateFuzzyEq(date1: string, date2: string, thresholdDays: number): boolean function dateFuzzyEq(date1: string, date2: string, thresholdDays: number): boolean
-- Convert the date strings to Lua date tables -- Convert the date strings to Lua date tables
local year1, month1, day1 = date1:match("(%d+)-(%d+)-(%d+)") local year1, month1, day1 = date1:match("(%d+)-(%d+)-(%d+)")
local year2, month2, day2 = date2:match("(%d+)-(%d+)-(%d+)") local year2, month2, day2 = date2:match("(%d+)-(%d+)-(%d+)")
-- Create date tables -- Create date tables
local dt1 = local dt1 =
os.time({ year = assert(tonumber(year1)), month = assert(tonumber(month1)), day = assert(tonumber(day1)) }) os.time({ year = assert(tonumber(year1)), month = assert(tonumber(month1)), day = assert(tonumber(day1)) })
local dt2 = local dt2 =
os.time({ year = assert(tonumber(year2)), month = assert(tonumber(month2)), day = assert(tonumber(day2)) }) os.time({ year = assert(tonumber(year2)), month = assert(tonumber(month2)), day = assert(tonumber(day2)) })
-- Calculate the difference in seconds -- Calculate the difference in seconds
local difference = math.abs(dt1 - dt2) local difference = math.abs(dt1 - dt2)
-- Calculate the threshold in seconds -- Calculate the threshold in seconds
local threshold_seconds = thresholdDays * 86400 -- 86400 seconds in a day local threshold_seconds = thresholdDays * 86400 -- 86400 seconds in a day
-- Check if the difference is within the threshold -- Check if the difference is within the threshold
return difference <= threshold_seconds return difference <= threshold_seconds
end end
function timeFuzzyEq(time1: string, time2: string, thresholdSeconds: number): boolean function timeFuzzyEq(time1: string, time2: string, thresholdSeconds: number): boolean
-- Convert the time strings to hours, minutes, and seconds -- Convert the time strings to hours, minutes, and seconds
local hour1, minute1 = time1:match("(%d+):(%d+)") local hour1, minute1 = time1:match("(%d+):(%d+)")
local hour2, minute2 = time2:match("(%d+):(%d+)") local hour2, minute2 = time2:match("(%d+):(%d+)")
-- Create time tables and convert to seconds -- Create time tables and convert to seconds
local totalSeconds1 = (assert(tonumber(hour1)) * 3600) + (assert(tonumber(minute1)) * 60) local totalSeconds1 = (assert(tonumber(hour1)) * 3600) + (assert(tonumber(minute1)) * 60)
local totalSeconds2 = (assert(tonumber(hour2)) * 3600) + (assert(tonumber(minute2)) * 60) local totalSeconds2 = (assert(tonumber(hour2)) * 3600) + (assert(tonumber(minute2)) * 60)
-- Calculate the difference in seconds -- Calculate the difference in seconds
local difference = math.abs(totalSeconds1 - totalSeconds2) local difference = math.abs(totalSeconds1 - totalSeconds2)
-- Check if the difference is within the threshold -- Check if the difference is within the threshold
return difference <= thresholdSeconds return difference <= thresholdSeconds
end end
return function(test: typeof(frktest.test)) return function(test: typeof(frktest.test))
test.suite("ZIP metadata tests", function() test.suite("ZIP metadata tests", function()
for _, file in ZIPS do for _, file in ZIPS do
if not string.match(file, "%.zip$") then if not string.match(file, "%.zip$") then
-- Not a zip file, skip -- Not a zip file, skip
continue continue
end end
local checkErr:(((...any) -> any?) -> nil) = if table.find(FALLIBLES, file) then check.should_error else check.should_not_error local checkErr: ((...any) -> any?) -> nil = if table.find(FALLIBLES, file)
test.case(`Parsed metadata matches unzip output - {file}`, function() then check.should_error
checkErr(function(...) else check.should_not_error
file = "tests/data/" .. file test.case(`Parsed metadata matches unzip output - {file}`, function()
local data = fs.readFile(file) checkErr(function(...)
local zip = unzip.load(buffer.fromstring(data)) file = "tests/data/" .. file
local data = fs.readFile(file)
-- Get sizes from unzip command local zip = unzip.load(buffer.fromstring(data))
local result = process.spawn("unzip", { "-v", file })
-- HACK: We use assert here since we don't know if we expect false or true -- Get sizes from unzip command
assert(result.ok) local result = process.spawn("unzip", { "-v", file })
-- HACK: We use assert here since we don't know if we expect false or true
-- Parse unzip output assert(result.ok)
for line in string.gmatch(result.stdout, "[^\r\n]+") do
if -- Parse unzip output
not string.match(line, "^Archive:") for line in string.gmatch(result.stdout, "[^\r\n]+") do
and not string.match(line, "^%s+Length") if
and not string.match(line, "^%s*%-%-%-%-") not string.match(line, "^Archive:")
and not string.match(line, "files?$") and not string.match(line, "^%s+Length")
and #line > 0 and not string.match(line, "^%s*%-%-%-%-")
then and not string.match(line, "files?$")
-- TODO: Expose information about size, and compression ratio in API and #line > 0
local length, method, _size, _cmpr, expectedDate, expectedTime, crc32, name = string.match( then
line, -- TODO: Expose information about size, and compression ratio in API
"^%s*(%d+)%s+(%S+)%s+(%d+)%s+([+-]?%d*%%?)%s+(%d%d%d%d%-%d%d%-%d%d)%s+(%d%d:%d%d)%s+(%x+)%s+(.+)$" local length, method, _size, _cmpr, expectedDate, expectedTime, crc32, name = string.match(
) line,
"^%s*(%d+)%s+(%S+)%s+(%d+)%s+([+-]?%d*%%?)%s+(%d%d%d%d%-%d%d%-%d%d)%s+(%d%d:%d%d)%s+(%x+)%s+(.+)$"
local entry = assert(zip:findEntry(assert(name))) )
local gotDateTime = DateTime.fromLocalTime( local entry = assert(zip:findEntry(assert(name)))
timestampToValues(entry.timestamp) :: DateTime.DateTimeValueArguments
) local gotDateTime = DateTime.fromLocalTime(
timestampToValues(entry.timestamp) :: DateTime.DateTimeValueArguments
check.equal(tonumber(length), entry.size) )
check.equal(METHOD_NAME_TRANSFORMATIONS[method :: string], entry.method)
check.is_true(dateFuzzyEq(gotDateTime:formatLocalTime("%Y-%m-%d"), expectedDate :: string, 1)) check.equal(tonumber(length), entry.size)
check.equal(METHOD_NAME_TRANSFORMATIONS[method :: string], entry.method)
-- TODO: Use extra datetime field check.is_true(
check.is_true( dateFuzzyEq(gotDateTime:formatLocalTime("%Y-%m-%d"), expectedDate :: string, 1)
-- Allow a threshold of 26 hours, which is the largest possible gap between any two )
-- timezones
timeFuzzyEq(gotDateTime:formatLocalTime("%H:%M"), expectedTime :: string, 26 * 3600) -- TODO: Use extra datetime field
) check.is_true(
-- Allow a threshold of 26 hours, which is the largest possible gap between any two
check.equal(string.format("%08x", entry.crc), crc32) -- timezones
end timeFuzzyEq(gotDateTime:formatLocalTime("%H:%M"), expectedTime :: string, 26 * 3600)
end )
return check.equal(string.format("%08x", entry.crc), crc32)
end) end
end) end
end
end) return
end end)
end)
end
end)
end

View file

@ -1,88 +1,88 @@
local fs = require("@lune/fs") local fs = require("@lune/fs")
local process = require("@lune/process") local process = require("@lune/process")
local frktest = require("../lune_packages/frktest") local frktest = require("../lune_packages/frktest")
local check = frktest.assert.check local check = frktest.assert.check
local ZipReader = require("../lib") local ZipReader = require("../lib")
local ZIPS = { local ZIPS = {
"tests/data/files_and_dirs.zip", "tests/data/files_and_dirs.zip",
"tests/data/symlink.zip", "tests/data/symlink.zip",
"tests/data/extended_timestamp.zip", "tests/data/extended_timestamp.zip",
} }
return function(test: typeof(frktest.test)) return function(test: typeof(frktest.test))
test.suite("ZIP walking tests", function() test.suite("ZIP walking tests", function()
test.case("Walks all entries recursively", function() test.case("Walks all entries recursively", function()
for _, file in ZIPS do for _, file in ZIPS do
local data = fs.readFile(file) local data = fs.readFile(file)
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
-- Get entries from our implementation -- Get entries from our implementation
local entries = {} local entries = {}
zip:walk(function(entry) zip:walk(function(entry)
if entry.name ~= "/" then if entry.name ~= "/" then
table.insert(entries, entry:getPath()) table.insert(entries, entry:getPath())
end end
end) end)
table.sort(entries) table.sort(entries)
-- Get entries from unzip command -- Get entries from unzip command
local result = process.spawn("unzip", { "-l", file }) local result = process.spawn("unzip", { "-l", file })
check.is_true(result.ok) check.is_true(result.ok)
-- Parse unzip output into sorted array -- Parse unzip output into sorted array
local unzipEntries = {} local unzipEntries = {}
for line in string.gmatch(result.stdout, "[^\r\n]+") do for line in string.gmatch(result.stdout, "[^\r\n]+") do
-- Skip header/footer lines and empty lines -- Skip header/footer lines and empty lines
if if
not string.match(line, "^Archive:") not string.match(line, "^Archive:")
and not string.match(line, "^%s+Length") and not string.match(line, "^%s+Length")
and not string.match(line, "^%s*%-%-%-%-") and not string.match(line, "^%s*%-%-%-%-")
and not string.match(line, "^%s+%d+%s+%d+ files?$") and not string.match(line, "^%s+%d+%s+%d+ files?$")
and #line > 0 and #line > 0
then then
-- Extract filename from unzip output format -- Extract filename from unzip output format
local name = string.match(line, "%S+$") local name = string.match(line, "%S+$")
if name then if name then
table.insert(unzipEntries, name) table.insert(unzipEntries, name)
end end
end end
end end
table.sort(unzipEntries) table.sort(unzipEntries)
-- Compare results -- Compare results
check.table.equal(entries, unzipEntries) check.table.equal(entries, unzipEntries)
end end
end) end)
test.case("Walks with correct depth values", function() test.case("Walks with correct depth values", function()
for _, file in ZIPS do for _, file in ZIPS do
local data = fs.readFile(file) local data = fs.readFile(file)
local zip = ZipReader.load(buffer.fromstring(data)) local zip = ZipReader.load(buffer.fromstring(data))
-- Verify depth values increase correctly -- Verify depth values increase correctly
local rootSeen = false local rootSeen = false
zip:walk(function(entry, depth) zip:walk(function(entry, depth)
if entry:getPath() == "/" then if entry:getPath() == "/" then
check.equal(depth, 0) check.equal(depth, 0)
rootSeen = true rootSeen = true
return return
end end
-- Count path separators to verify depth, starting at 1 for `/` -- Count path separators to verify depth, starting at 1 for `/`
local expectedDepth = 1 local expectedDepth = 1
for _ in string.gmatch(entry:getPath():gsub("/$", ""), "/") do for _ in string.gmatch(entry:getPath():gsub("/$", ""), "/") do
expectedDepth += 1 expectedDepth += 1
end end
check.equal(depth, expectedDepth) check.equal(depth, expectedDepth)
end) end)
check.is_true(rootSeen) check.is_true(rootSeen)
end end
end) end)
end) end)
end end