Browse Source

punk::zip 0.3.0: punkzip extraction accelerator + parity suite (G-126); 0.28.4

The final G-126 acceptance clauses:

- punk::zip::accelerator (auto|none|<path>): auto probes
  env(PUNKZIP_EXE) then a punkzip beside [info nameofexecutable];
  resolution cached until reconfigured
- unzip hands whole-archive member WRITING to the accelerator only when
  the call is one it serves identically (globs {*}, no excludes,
  default -overwrite/-mtime/-verify, ascii member names) and re-stamps
  mtimes with punk::zip's local-time convention afterwards, so the two
  engines produce identical trees; preflight refusals, member selection
  and the returned names always come from punk::zip's own reader (the
  G-124 floor, unchanged); accelerator failure falls back to pure Tcl
  silently; last_unzip_engine/last_accelerator_note expose the per-call
  decision
- parity suite zipaccel.test (9 tests): identical names, bytes and
  mtimes through both engines over the G-124 shapes and whole-kit
  extraction of the file-relative tclsh90b4_piperepl.exe (841 members);
  listings engine-independent; punkzip porcelain agrees with punk::zip
  member dicts on every shared field (mtime excluded by design - UTC vs
  local convention); pure path proven when the binary is absent;
  selective calls proven to run pure. Implicit directories (kits store
  no dir entries) are excluded from mtime comparison - neither engine
  stamps them
- recorded benchmark (in the goal detail): accelerator fixed cost
  ~9-13ms, crossover ~15-20 members, material (>= x1.7) from ~50;
  x2.3-2.6 at kit scale under a native tclsh and x3.8-4.9 kit-hosted
  (punk91.exe 21.9s -> 5.7s) - the bake-from-kit case the goal
  motivated
- full runtests at recorded baseline under tclsh9.0.5-punk: 1186/1211,
  1 failed = the pre-existing exec-14.3 baseline

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 4 days ago
parent
commit
df6642e189
  1. 15
      CHANGELOG.md
  2. 48
      goals/G-126-punkzip-accelerator.md
  3. 2
      punkproject.toml
  4. 169
      src/modules/punk/zip-999999.0a1.0.tm
  5. 13
      src/modules/punk/zip-buildversion.txt
  6. 271
      src/tests/modules/punk/zip/testsuites/zip/zipaccel.test

15
CHANGELOG.md

@ -5,6 +5,21 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.28.4] - 2026-07-27
- punk::zip 0.3.0 (G-126): optional punkzip extraction accelerator. New
`punk::zip::accelerator` (auto|none|<path>; auto probes env(PUNKZIP_EXE) then a
punkzip beside the executable, resolution cached); `unzip` hands whole-archive member
writing to the binary when the call is one it serves identically (whole archive,
default -overwrite/-mtime/-verify, ascii names) and re-stamps mtimes to punk::zip's
local-time convention afterwards, falling back to pure Tcl silently otherwise
(punk::zip::last_unzip_engine / last_accelerator_note record the decision). Preflight
refusals, member selection and returned names always come from punk::zip's own
reader. Parity suite src/tests/modules/punk/zip/testsuites/zip/zipaccel.test proves
identical names, bytes and mtimes through both engines over the G-124 shapes and the
real file-relative kit runtime. Measured: material from ~50 members; x2.3-2.6 at kit
scale under a native tclsh; x3.8-4.9 kit-hosted (e.g. punk91.exe 21.9s -> 5.7s).
## [0.28.3] - 2026-07-27
- New `make.tcl tool` subcommand (G-126): surface for the vendored first-party zig

48
goals/G-126-punkzip-accelerator.md

@ -279,6 +279,48 @@ working tree; one now exists.
(user decision 2026-07-27). The acceptance's build-step clause is satisfied:
bin/punkzip.exe is reproducible on a clean checkout from vendored source + pinned
toolchain + this step.
- Remaining for acceptance: punk::zip accelerator detection + fast path behind the
G-124 floor; parity suite in `src/tests/modules/punk/zip/`; recorded benchmark
(speedup + entry count where it becomes material).
- 2026-07-27 punk::zip fast path + parity suite + benchmark LANDED (punkshell 0.28.4,
punk::zip 0.3.0) - the final acceptance clauses:
- `punk::zip::accelerator` (auto|none|<path>): auto probes env(PUNKZIP_EXE) then a
punkzip beside [info nameofexecutable]; resolution cached until reconfigured.
`unzip` hands whole-archive member WRITING to the accelerator only when the call
is one it serves identically (globs {*}, no excludes, default
-overwrite/-mtime/-verify, ascii names) and re-stamps mtimes with punk::zip's
local-time convention afterwards so the engines produce identical trees;
preflight refusals, member selection and returned names always come from
punk::zip's own reader (the G-124 floor, unchanged and still sole authority);
any accelerator failure falls back to pure Tcl silently.
punk::zip::last_unzip_engine / last_accelerator_note expose the per-call
decision for tests and diagnostics.
- Parity suite `src/tests/modules/punk/zip/testsuites/zip/zipaccel.test` (9 tests,
green; accelerator-dependent cases self-gate on bin/punkzip): identical names,
bytes AND mtimes through both engines over the G-124 shapes (plain,
archive-relative, file-relative) and whole-kit extraction of the recorded
file-relative `tclsh90b4_piperepl.exe` (841 members); listings engine-independent;
porcelain listing agrees with punk::zip member dicts on every shared field
(mtime excluded from the crosscheck by design: porcelain is the tz-free UTC
interpretation, punk::zip members the local-time convention - see the timestamp
Notes bullet); pure path proven taken when the binary is absent; selective calls
proven to run pure silently. Implicit directories (kits store no dir entries)
are excluded from mtime comparison - neither engine stamps them.
- Recorded benchmark (best of 3, whole-archive `punk::zip::unzip`, this machine
2026-07-27, bin/punkzip.exe v2.3.1 ReleaseSafe):
native Git tclsh 8.6.11:
info-zip fixture 4 members pure 4.8ms accel 13.6ms x0.35
synthetic tiny 50 members pure 71.7ms accel 41.1ms x1.74
synthetic tiny 200 members pure 286.9ms accel 126.4ms x2.27
synthetic tiny 500 members pure 709.9ms accel 303.7ms x2.34
piperepl.exe 841 members pure 1328.3ms accel 503.6ms x2.64
punk91.exe (49MB) 3629 members pure 6670.1ms accel 2837.1ms x2.35
kit-hosted (punk902z script, tcl 9.0.2, punk stack loaded - the bake-from-kit
scenario that motivated this goal):
piperepl.exe 841 members pure 4772.1ms accel 968.9ms x4.93
punk91.exe 3629 members pure 21850.9ms accel 5683.5ms x3.84
Accelerator fixed cost is ~9-13ms (process spawn + its own archive read);
crossover vs pure is ~15-20 members; it becomes MATERIAL (>= x1.7) from roughly
50 members, and the kit-hosted case gains most (3.8-16s per kit extraction).
- Full runtests suite at recorded baseline under the native tclsh9.0.5-punk
runtime: 1186/1211 passed, 24 skipped, 1 failed = the pre-existing exec-14.3
baseline. (The Git tclsh is not a full-suite runner - 194 environmental
failures there predate this work.)
ACCEPTANCE FULLY MET - flipped achieved 2026-07-27 as part of this closeout.

2
punkproject.toml

@ -1,4 +1,4 @@
[project]
name = "punkshell"
version = "0.28.3"
version = "0.28.4"
license = "BSD-2-Clause"

169
src/modules/punk/zip-999999.0a1.0.tm

@ -66,7 +66,15 @@ package require punk::args
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::zip {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
#G-126 accelerator state - see punk::zip::accelerator and punk::zip::unzip.
#The pure-Tcl reader is the always-available floor; the vendored punkzip
#binary is an optional per-call extraction accelerator.
variable accelerator_config auto ;#auto | none | <path to punkzip executable>
variable accelerator_resolved ""
variable accelerator_resolved_for "\uFFFF" ;#config value the cached resolution was computed for
variable last_unzip_engine "" ;#tcl | accelerated - which engine the last unzip used
variable last_accelerator_note "" ;#why the last unzip skipped the accelerator or fell back
#*** !doctools
#[subsection {Namespace punk::zip}]
@ -1104,6 +1112,79 @@ tcl::namespace::eval punk::zip {
return [Select_members [dict get $arc members] $globs $excludes]
}
punk::args::define {
@id -id ::punk::zip::accelerator
@cmd -name punk::zip::accelerator\
-summary\
"Query or configure the optional punkzip extraction accelerator"\
-help\
"punk::zip can hand whole-archive extraction to the vendored punkzip
tool (goal G-126; built to bin/ by 'make.tcl tool build punkzip')
when a usable binary is present - materially faster for large member
counts - while the pure-Tcl reader remains the always-available
floor and the authority on preflight refusals, member selection and
returned names.
With no argument, returns the currently resolved accelerator path,
or an empty string when none is usable. With an argument, sets the
configuration and returns the new resolution:
auto - (default) probe: env(PUNKZIP_EXE) if set, else a punkzip
executable beside [info nameofexecutable]
none - disable the accelerator (pure-Tcl always)
<path> - use the punkzip binary at an explicit path
Resolution is cached until the configuration changes. See
punk::zip::unzip for when the accelerator is actually used: calls it
cannot serve identically run pure-Tcl, and an accelerator failure
falls back to pure-Tcl silently. For diagnostics and tests,
punk::zip::last_unzip_engine records which engine the last unzip
used (tcl|accelerated) and punk::zip::last_accelerator_note records
why the accelerator was skipped or abandoned."
@values -min 0 -max 1
config -optional 1 -default "" -help\
"auto | none | path of a punkzip executable.
Empty (or omitted) queries without changing the configuration."
}
proc accelerator {args} {
set argd [punk::args::parse $args withid ::punk::zip::accelerator]
variable accelerator_config
variable accelerator_resolved
variable accelerator_resolved_for
set config [dict get $argd values config]
if {$config ne ""} {
set accelerator_config $config
}
if {$accelerator_resolved_for eq $accelerator_config} {
return $accelerator_resolved
}
set resolved ""
set candidates [list]
switch -exact -- $accelerator_config {
none {}
auto {
if {[info exists ::env(PUNKZIP_EXE)] && $::env(PUNKZIP_EXE) ne ""} {
lappend candidates $::env(PUNKZIP_EXE)
} else {
set exedir [file dirname [info nameofexecutable]]
lappend candidates [file join $exedir punkzip.exe] [file join $exedir punkzip]
}
}
default {
lappend candidates $accelerator_config
}
}
foreach c $candidates {
#file executable is unreliable for some windows setups - existence suffices there
if {[file isfile $c] && ([file executable $c] || $::tcl_platform(platform) eq "windows")} {
set resolved [file normalize $c]
break
}
}
set accelerator_resolved $resolved
set accelerator_resolved_for $accelerator_config
return $resolved
}
punk::args::define {
@id -id ::punk::zip::unzip
@cmd -name punk::zip::unzip\
@ -1124,6 +1205,15 @@ tcl::namespace::eval punk::zip {
naming the reason instead of leaving a half-populated directory. A
member whose path would escape targetdir is refused the same way.
When the punkzip accelerator is available (see punk::zip::accelerator)
and the call is one it serves identically - whole archive, default
-overwrite/-mtime/-verify, ascii member names - the member data is
written by the accelerator instead of the pure-Tcl loop, with mtimes
re-stamped to this module's convention afterwards; preflight, member
selection and the returned names always come from punk::zip's own
reader, and any accelerator failure falls back to pure Tcl silently.
punk::zip::last_unzip_engine records which engine ran.
Returns the list of member names extracted (see -return).
Examples:
@ -1178,6 +1268,11 @@ tcl::namespace::eval punk::zip {
set restoremtime [dict get $argd opts -mtime]
set verify [dict get $argd opts -verify]
variable last_unzip_engine
variable last_accelerator_note
set last_unzip_engine tcl
set last_accelerator_note ""
set arc [Open_archive $zipfile 1]
set chan [dict get $arc chan]
set extracted [list]
@ -1186,6 +1281,7 @@ tcl::namespace::eval punk::zip {
set selected [Select_members [dict get $arc members] $globs $excludes]
#preflight - nothing is written until every selected member is known good
set targets [list]
set names_ascii 1
foreach m $selected {
set reason [Member_unsupported_reason $m]
if {$reason ne ""} {
@ -1195,24 +1291,71 @@ tcl::namespace::eval punk::zip {
if {!$overwrite && !([dict get $m isdirectory]) && [file exists $target]} {
error "punk::zip::unzip: '$target' already exists and -overwrite is 0"
}
if {![string is ascii [dict get $m name]]} {
set names_ascii 0
}
lappend targets $target
}
file mkdir $targetdir
foreach m $selected target $targets {
set name [dict get $m name]
if {[dict get $m isdirectory]} {
file mkdir $target
if {$restoremtime && [dict get $m mtime]} {
lappend dirtimes $target [dict get $m mtime]
}
#G-126 accelerator: hand whole-archive member WRITING to the vendored
#punkzip binary when this call is one it serves identically. punk::zip's
#own parse above stays authoritative for preflight refusals, member
#selection and the returned names; the pure-Tcl loop below is the
#always-available floor and any accelerator failure falls back to it
#silently. Eligibility: whole archive (globs {*}, no excludes), the
#default -overwrite/-mtime/-verify semantics punkzip matches (it always
#overwrites, restores times and crc-verifies), and ascii member names
#(punkzip's name handling is wtf-8; punk::zip decodes cp437/utf-8 flags).
#punkzip stamps mtimes with its tz-free utc convention, so after a
#successful run the members are re-stamped below with this module's
#local-time convention - the two engines produce identical trees.
set accelerated 0
if {$verify && $overwrite && $restoremtime && $names_ascii
&& [llength $excludes] == 0 && [llength $globs] == 1 && [lindex $globs 0] eq "*"} {
set acc [accelerator]
if {$acc eq ""} {
set last_accelerator_note "no accelerator binary resolved (see punk::zip::accelerator)"
} elseif {[catch {exec $acc extract -d $targetdir $zipfile 2>@1} accout]} {
set last_accelerator_note "accelerator '$acc' failed - fell back to pure tcl: [string range $accout 0 300]"
} else {
file mkdir [file dirname $target]
Extract_member $chan $m $target $verify
if {$restoremtime && [dict get $m mtime]} {
catch {file mtime $target [dict get $m mtime]}
set accelerated 1
}
} else {
set last_accelerator_note "call not accelerator-eligible (selective, non-default options, or non-ascii names) - pure tcl"
}
if {$accelerated} {
set last_unzip_engine accelerated
foreach m $selected target $targets {
if {[dict get $m isdirectory]} {
if {[dict get $m mtime]} {
lappend dirtimes $target [dict get $m mtime]
}
} else {
if {[dict get $m mtime]} {
catch {file mtime $target [dict get $m mtime]}
}
}
lappend extracted [dict get $m name]
}
} else {
foreach m $selected target $targets {
set name [dict get $m name]
if {[dict get $m isdirectory]} {
file mkdir $target
if {$restoremtime && [dict get $m mtime]} {
lappend dirtimes $target [dict get $m mtime]
}
} else {
file mkdir [file dirname $target]
Extract_member $chan $m $target $verify
if {$restoremtime && [dict get $m mtime]} {
catch {file mtime $target [dict get $m mtime]}
}
}
lappend extracted $name
}
lappend extracted $name
}
} finally {
close $chan

13
src/modules/punk/zip-buildversion.txt

@ -1,6 +1,17 @@
0.2.0
0.3.0
#First line must be a semantic version number
#all other lines are ignored.
#0.3.0 - optional punkzip extraction accelerator (G-126): new punk::zip::accelerator
# (auto|none|<path>; auto probes env(PUNKZIP_EXE) then a punkzip beside
# [info nameofexecutable], resolution cached). unzip hands whole-archive member
# WRITING to the accelerator when the call is one it serves identically (globs {*},
# no excludes, default -overwrite/-mtime/-verify, ascii names) and re-stamps mtimes
# with this module's local-time convention afterwards so the engines produce
# identical trees; preflight refusals, member selection and returned names always
# come from punk::zip's own reader, and accelerator failure falls back to pure Tcl
# silently. Diagnostics: punk::zip::last_unzip_engine (tcl|accelerated) and
# punk::zip::last_accelerator_note. Parity suite:
# src/tests/modules/punk/zip/testsuites/zip/zipaccel.test.
#0.2.0 - dependency-free reading (G-124): archive_info/members/unzip read a plain zip or an
# executable-prefixed archive under either offset convention using stock Tcl only - no
# zipfs, no vfs::zip, no tcllib. Base-offset derivation factored out of extract_preamble

271
src/tests/modules/punk/zip/testsuites/zip/zipaccel.test

@ -0,0 +1,271 @@
package require tcltest
package require punk::zip
#added 2026-07-27 (agent, G-126) - parity suite for the punkzip extraction accelerator.
#Proves the accelerated and pure-Tcl unzip paths produce IDENTICAL results (member name
#lists, byte-identical extraction trees, matching file AND directory mtimes) over the
#G-124 fixture shapes (plain / archive-relative prefixed / file-relative prefixed, one
#source tree), that listings are engine-independent, that punkzip's porcelain listing
#agrees with punk::zip's member dicts on every shared field, that the pure-Tcl path is
#taken when the accelerator binary is absent, and that calls the accelerator cannot
#serve identically (selective globs) silently run pure-Tcl.
#The accelerator-dependent tests self-gate on bin/punkzip existing ('make.tcl tool
#build punkzip' builds it); the pure-path-when-absent proof runs everywhere.
#mtime NOTE: porcelain mtimes are punkzip's tz-free UTC interpretation of the DOS
#fields while punk::zip's member mtimes use the local-time convention, so the
#porcelain crosscheck compares every shared field EXCEPT mtime (recorded in the
#G-126 goal notes); extraction-tree mtimes ARE compared - the accelerated path
#re-stamps them with punk::zip's convention, so the engines must agree exactly.
namespace eval ::testspace {
namespace import ::tcltest::*
variable BASE [makeDirectory g126_zipaccel]
variable projectroot [file normalize [file join [file dirname [info script]] .. .. .. .. .. .. ..]]
variable ACCEL ""
foreach _cand [list [file join $projectroot bin punkzip.exe] [file join $projectroot bin punkzip]] {
if {[file isfile $_cand]} {
set ACCEL $_cand
break
}
}
testConstraint punkzipavailable [expr {$ACCEL ne ""}]
#the recorded file-relative runtime named in the G-126 Acceptance (same pin as
#zipreader.test). Not in the repo (bin/ build outputs are untracked) so it self-gates.
variable PIPEREPL [file join $projectroot bin runtime win32-x86_64 tclsh90b4_piperepl.exe]
testConstraint piperepl_runtime [file exists $PIPEREPL]
proc fwrite {path content} {
file mkdir [file dirname $path]
set fd [open $path wb]
puts -nonewline $fd $content
close $fd
}
proc fread {path} {
set fd [open $path rb]
set data [read $fd]
close $fd
return $data
}
#paths whose mtimes the engines actually STAMP: members with a representable
#stored time. Anything else (implicit intermediate directories - e.g. the
#piperepl kit stores no directory entries at all - or zero-mtime members) gets
#extraction wall-time from either engine, which legitimately differs between
#two runs and must not be compared.
proc stampset {archive} {
set s [dict create]
foreach m [punk::zip::members $archive] {
if {[dict get $m mtime]} {
dict set s [string trimright [dict get $m name] /] 1
}
}
return $s
}
#recursive snapshot: relpath -> dir/file record incl. content crc and mtime
#(mtime recorded as 'unstamped' for paths outside the stamped set)
proc treesnap {base stamped {rel ""}} {
set snap [dict create]
set dir [expr {$rel eq "" ? $base : [file join $base $rel]}]
foreach p [lsort [glob -nocomplain -dir $dir *]] {
set name [file tail $p]
set r [expr {$rel eq "" ? $name : "$rel/$name"}]
set mt [expr {[dict exists $stamped $r] ? [file mtime $p] : "unstamped"}]
if {[file isdirectory $p]} {
dict set snap $r [list dir mtime $mt]
set snap [dict merge $snap [treesnap $base $stamped $r]]
} else {
dict set snap $r [list file size [file size $p] crc [format %08x [zlib crc32 [fread $p]]] mtime $mt]
}
}
return $snap
}
#empty string when identical; otherwise names the first difference
proc snapdiff {a b} {
set ka [lsort [dict keys $a]]
set kb [lsort [dict keys $b]]
if {$ka ne $kb} {
set onlya [list]
set onlyb [list]
foreach k $ka {if {![dict exists $b $k]} {lappend onlya $k}}
foreach k $kb {if {![dict exists $a $k]} {lappend onlyb $k}}
return "keysets differ - only-first:$onlya only-second:$onlyb"
}
dict for {k v} $a {
if {$v ne [dict get $b $k]} {
return "entry '$k' differs: $v vs [dict get $b $k]"
}
}
return ""
}
# -- --- --- fixtures: the G-124 shapes (one source tree, packed three ways) --- --- --
variable SRC [file join $BASE srctree]
variable PREAMBLE [file join $BASE preamble.bin]
variable PLAIN [file join $BASE plain.zip]
variable ARCREL [file join $BASE arcrel.bin]
variable FILEREL [file join $BASE filerel.bin]
file delete -force $SRC
file mkdir $SRC/sub/deeper
fwrite $SRC/hello.txt "hello world\n"
fwrite $SRC/sub/nested.tcl "puts nested\n"
set fixturebytes ""
for {set i 0} {$i < 256} {incr i} {
append fixturebytes [binary format c $i]
}
fwrite $SRC/binary.dat $fixturebytes
set BIGTEXT ""
for {set i 0} {$i < 40000} {incr i} {
append BIGTEXT "line $i of a member large enough to force the streaming path\n"
}
fwrite $SRC/sub/deeper/big.txt $BIGTEXT
fwrite $PREAMBLE [string repeat "PREAMBLE-BYTES-\x00\x01\x02\x50\x4b\x05\x06" 4096]
punk::zip::mkzip -return none -directory $SRC -- $PLAIN *
punk::zip::mkzip -return none -offsettype archive -runtime $PREAMBLE -directory $SRC -- $ARCREL *
punk::zip::mkzip -return none -offsettype file -runtime $PREAMBLE -directory $SRC -- $FILEREL *
#run one extraction under an explicit accelerator configuration; returns a dict
#with the extracted names, the engine that ran, the fallback note and a snapshot
proc extract_with {config archive outdir args} {
variable BASE
set target [file join $BASE $outdir]
file delete -force $target
punk::zip::accelerator $config
set names [punk::zip::unzip $archive $target {*}$args]
set r [dict create\
names [lsort $names]\
engine $::punk::zip::last_unzip_engine\
note $::punk::zip::last_accelerator_note\
snap [treesnap $target [stampset $archive]]\
]
punk::zip::accelerator auto
return $r
}
# -- --- --- accelerator resolution --- --- --
test zipaccel_resolves_explicit_path {an explicit accelerator path resolves and none disables} -constraints {punkzipavailable} -body {
variable ACCEL
set result [list]
lappend result explicit [expr {[punk::zip::accelerator $ACCEL] ne ""}]
lappend result none [punk::zip::accelerator none]
punk::zip::accelerator auto
set result
} -result {explicit 1 none {}}
test zipaccel_pure_path_when_absent {a missing accelerator binary resolves empty and unzip runs pure-Tcl successfully} -body {
variable PLAIN
set r [extract_with [file join $::testspace::BASE nosuch punkzip.exe] $PLAIN out_absent]
list resolved_empty [expr {$::punk::zip::accelerator_resolved eq ""}]\
engine [dict get $r engine]\
extracted_count [llength [dict get $r names]]\
note_names_it [string match "*no accelerator binary resolved*" [dict get $r note]]
} -result {resolved_empty 1 engine tcl extracted_count 6 note_names_it 1}
# -- --- --- listings are engine-independent --- --- --
test zipaccel_listing_engine_independent {members and archive_info return identical results whatever the accelerator configuration} -constraints {punkzipavailable} -body {
variable PLAIN
variable ACCEL
punk::zip::accelerator none
set m1 [punk::zip::members $PLAIN]
set i1 [punk::zip::archive_info $PLAIN]
punk::zip::accelerator $ACCEL
set m2 [punk::zip::members $PLAIN]
set i2 [punk::zip::archive_info $PLAIN]
punk::zip::accelerator auto
list members_equal [expr {$m1 eq $m2}] info_equal [expr {$i1 eq $i2}]
} -result {members_equal 1 info_equal 1}
# -- --- --- porcelain crosscheck: two independent readers agree --- --- --
test zipaccel_porcelain_crosscheck {punkzip's porcelain listing agrees with punk::zip's member dicts on every shared field} -constraints {punkzipavailable} -body {
variable PLAIN
variable ACCEL
set out [exec $ACCEL list -porcelain $PLAIN 2>@1]
set porc [dict create]
set sawversion 0
foreach line [split $out \n] {
set line [string trimright $line \r]
if {$line eq "" || [string range $line 0 1] eq "# "} {continue}
if {!$sawversion} {
if {$line eq "punkzip_porcelain_v1"} {set sawversion 1}
continue
}
if {[regexp {^m (\d) (\S+) (\d+) (\d+) ([0-9a-f]{8}) (-?\d+) (\d) (\d+) (\d+) (.*)$} $line -> isdir methodname uncomp comp crc mtime enc madeby namelen name]} {
dict set porc $name [dict create isdir $isdir methodname $methodname size $uncomp csize $comp crc $crc encrypted $enc madeby $madeby namelen $namelen]
}
}
set result [list sawversion $sawversion]
set members [punk::zip::members $PLAIN]
lappend result member_count_equal [expr {[dict size $porc] == [llength $members]}]
set mismatches [list]
foreach m $members {
set name [dict get $m name]
if {![dict exists $porc $name]} {
lappend mismatches "missing-from-porcelain:$name"
continue
}
set p [dict get $porc $name]
foreach {pkey mkey conv} {isdir isdirectory {} methodname methodname {} size size {} csize csize {} encrypted encrypted {} madeby madeby {}} {
set mv [dict get $m $mkey]
if {[dict get $p $pkey] ne $mv} {
lappend mismatches "$name:$pkey=[dict get $p $pkey]!=$mv"
}
}
if {[dict get $p crc] ne [format %08x [dict get $m crc]]} {
lappend mismatches "$name:crc"
}
if {[dict get $p namelen] != [string length $name]} {
lappend mismatches "$name:namelen"
}
}
lappend result mismatches $mismatches
set result
} -result {sawversion 1 member_count_equal 1 mismatches {}}
# -- --- --- extraction parity over the three shapes --- --- --
foreach {shapename shapevar} {plain PLAIN archiverelative ARCREL filerelative FILEREL} {
test zipaccel_unzip_parity_$shapename "accelerated and pure-Tcl extraction of the $shapename shape produce identical names, bytes and mtimes" -constraints {punkzipavailable} -body [string map [list %V% $shapevar] {
variable %V%
variable ACCEL
set pure [extract_with none [set %V%] out_pure]
set fast [extract_with $ACCEL [set %V%] out_fast]
list engines [list [dict get $pure engine] [dict get $fast engine]]\
names_equal [expr {[dict get $pure names] eq [dict get $fast names]}]\
treediff [snapdiff [dict get $pure snap] [dict get $fast snap]]
}] -result {engines {tcl accelerated} names_equal 1 treediff {}}
}
# -- --- --- per-call decision: ineligible calls silently run pure --- --- --
test zipaccel_selective_call_runs_pure {a selective (glob) extraction is not accelerator-eligible and silently runs pure-Tcl} -constraints {punkzipavailable} -body {
variable PLAIN
variable ACCEL
set r [extract_with $ACCEL $PLAIN out_selective sub/*]
list engine [dict get $r engine]\
names [dict get $r names]\
note_names_it [string match "*not accelerator-eligible*" [dict get $r note]]
} -result {engine tcl names {sub/ sub/deeper/ sub/deeper/big.txt sub/nested.tcl} note_names_it 1}
# -- --- --- the real file-relative prefixed executable --- --- --
test zipaccel_piperepl_parity {whole-kit extraction of the recorded file-relative runtime is identical through both engines} -constraints {punkzipavailable piperepl_runtime} -body {
variable PIPEREPL
variable ACCEL
set pure [extract_with none $PIPEREPL out_rt_pure]
set fast [extract_with $ACCEL $PIPEREPL out_rt_fast]
list engines [list [dict get $pure engine] [dict get $fast engine]]\
member_count [llength [dict get $pure names]]\
names_equal [expr {[dict get $pure names] eq [dict get $fast names]}]\
treediff [snapdiff [dict get $pure snap] [dict get $fast snap]]
} -result {engines {tcl accelerated} member_count 841 names_equal 1 treediff {}}
cleanupTests
}
namespace delete ::testspace
Loading…
Cancel
Save