Files
rose-ash/lib/gitea/tests/wire.sx
giles 8ed44f7770 lib/gitea: fix the fetch-pack-over-HTTP hang — native parse fast path
The sx-forge native-loop blocker: clone! of the live giles/rose-ash
never returned over gitea/http-app. Root cause was NOT the transport —
pack-line-parse ran every pack line through the interpreted spec parser
(~6.6KB/s on the CEK machine; a full-repo pack = hours), and a non-hex
byte in a pkt length header parsed negative (index-of -1), walking the
scan index backwards forever.

- gitea/parse-obj: use the host reader (open-input-string + read,
  ~3700x faster, value-identical) when the host provides it; hosts
  without string ports keep sx-parse. Feature-detected at load.
- pkt-sections-loop: (< n 4) guard — malformed lengths error instead
  of hanging.
- push-cmd!: haves = every advertised remote ref held locally, so a
  NEW branch pushes only its delta, not the whole repo closure.
- tests/wire.sx: malformed-len errors, truncated-pkt clamps, parse-obj
  = sx-parse equivalence (blob/commit + cid). 83/83.
- tests/wire-http.sh + wire-http-client.sx: end-to-end over REAL
  http-listen/http-request on :8943 — ls-remote/clone/push-new-branch/
  fresh-clone-verify/delete. The coverage gap that hid all this.

Proven vs the live forge (in sx-gitea-1): full 4468-file clone in 77s
(was: hang), commit, push heads/sx-smoke-test ok, branch advertised on
sx.sx-web.org. Conformance 620/620.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:30:59 +00:00

522 lines
15 KiB
Plaintext

; lib/gitea/tests/wire.sx — Phase 3: pkt-line framing, object closure,
; smart-HTTP endpoints (info/refs, upload-pack, receive-pack), and the
; client (ls-remote/clone!/fetch!/push!) syncing two in-memory forges.
(define gitea-wire-pass 0)
(define gitea-wire-fail 0)
(define gitea-wire-fails (list))
(define
gitea-wire-test
(fn
(name actual expected)
(if
(= actual expected)
(set! gitea-wire-pass (+ gitea-wire-pass 1))
(begin
(set! gitea-wire-fail (+ gitea-wire-fail 1))
(set! gitea-wire-fails (append gitea-wire-fails (list {:name name :expected (inspect expected) :actual (inspect actual)})))))))
; ── pkt-line framing ─────────────────────────────────────────────────
(gitea-wire-test "hex4 small" (gitea/hex4 5) "0005")
(gitea-wire-test "hex4 max" (gitea/hex4 65535) "ffff")
(gitea-wire-test "hex4-parse" (gitea/hex4-parse "001a") 26)
(gitea-wire-test
"hex4 round trip"
(gitea/hex4-parse (gitea/hex4 4242))
4242)
(gitea-wire-test "pkt frames with length" (gitea/pkt "hi") "0006hi")
(gitea-wire-test
"pkt-sections round trip"
(gitea/pkt-sections (gitea/pkt-render (list (list "a" "bc") (list "z"))))
(list (list "a" "bc") (list "z")))
(gitea-wire-test "pkt-sections empty" (gitea/pkt-sections "") (list))
(gitea-wire-test
"pkt-sections flush only"
(gitea/pkt-sections "0000")
(list (list)))
(gitea-wire-test "pkt-fits? small" (gitea/pkt-fits? "x") true)
(define
gw-big
(reduce
(fn (acc i) (str acc acc))
"xxxxxxxxxx"
(list
1
2
3
4
5
6
7
8
9
10
11
12
13)))
(gitea-wire-test "pkt-fits? huge payload" (gitea/pkt-fits? gw-big) false)
; ── forge A with history ─────────────────────────────────────────────
(define gw-db (persist/mem-backend))
(define gw-forge (gitea/forge gw-db))
(gitea/user-create! gw-forge "alice")
(gitea/user-create! gw-forge "bob")
(gitea/user-create! gw-forge "rope")
(gitea/token-create! gw-forge "alice" "tok-a")
(gitea/token-create! gw-forge "bob" "tok-b")
(gitea/token-create! gw-forge "rope" "tok-r")
(gitea/repo-create! gw-forge "alice" "lib" {})
(gitea/collab-add! gw-forge "alice" "lib" "rope" "read")
(define gw-ga (gitea/repo-git gw-forge "alice" "lib"))
(git/add! gw-ga "README.md" "hello\n")
(git/add! gw-ga "src/a.txt" "alpha\n")
(define gw-c1 (git/commit! gw-ga {:message "init" :time 1 :author "alice"}))
(git/add! gw-ga "src/a.txt" "alpha2\n")
(define gw-c2 (git/commit! gw-ga {:message "more" :time 2 :author "alice"}))
(git/tag! gw-ga "v1" {:message "rel" :time 3})
(define gw-tag-cid (git/tag-get gw-ga "v1"))
(git/branch-create! gw-ga "dev" gw-c1)
; ── closure ──────────────────────────────────────────────────────────
; c1 = commit + root tree + src tree + 2 blobs
(gitea-wire-test
"closure of c1"
(len (gitea/closure-list gw-ga (list gw-c1)))
5)
; c2 adds commit + new root + new src + new blob
(gitea-wire-test
"closure of c2"
(len (gitea/closure-list gw-ga (list gw-c2)))
9)
(gitea-wire-test
"pack-cids c2 given c1"
(len (gitea/pack-cids gw-ga (list gw-c2) (list gw-c1)))
4)
(gitea-wire-test
"pack includes tip commit"
(contains? (gitea/pack-cids gw-ga (list gw-c2) (list gw-c1)) gw-c2)
true)
(gitea-wire-test
"pack excludes had commit"
(contains? (gitea/pack-cids gw-ga (list gw-c2) (list gw-c1)) gw-c1)
false)
(gitea-wire-test
"closure through tag object"
(len (gitea/closure-list gw-ga (list gw-tag-cid)))
10)
(gitea-wire-test
"closure-complete? tip"
(gitea/closure-complete? gw-ga (list gw-c2))
true)
(gitea-wire-test
"closure-complete? missing cid"
(gitea/closure-complete? gw-ga (list "sx1:doesnotexist"))
false)
(gitea-wire-test
"obj-refs of commit"
(len (gitea/obj-refs (git/read gw-ga gw-c2)))
2)
; ── pack line verification ───────────────────────────────────────────
(gitea-wire-test
"pack-line parses and verifies"
(get (gitea/pack-line-parse (gitea/pack-line gw-ga gw-c1)) :cid)
gw-c1)
(gitea-wire-test
"pack-line tamper detected"
(get
(gitea/pack-line-parse (str gw-c1 " " (serialize (git/blob "evil"))))
:error)
"cid-mismatch")
(gitea-wire-test
"unpack! rejects tampered pack"
(get
(gitea/unpack!
gw-ga
(list (str gw-c1 " " (serialize (git/blob "evil")))))
:error)
"cid-mismatch")
; ── server endpoints ─────────────────────────────────────────────────
(define gw-app (gitea/forge-app gw-forge))
(define gw-hdr (fn (tok) (if (nil? tok) {} {:authorization (str "Bearer " tok)})))
(define
gw-get
(fn (target tok) (gw-app (dream-request "GET" target (gw-hdr tok) ""))))
(define
gw-post
(fn
(target tok body)
(gw-app (dream-request "POST" target (gw-hdr tok) body))))
(gitea-wire-test
"info/refs 200"
(dream-status (gw-get "/alice/lib/info/refs" nil))
200)
(gitea-wire-test
"info/refs advertises main"
(contains?
(dream-resp-body (gw-get "/alice/lib/info/refs" nil))
(str gw-c2 " heads/main"))
true)
(gitea-wire-test
"info/refs advertises dev"
(contains?
(dream-resp-body (gw-get "/alice/lib/info/refs" nil))
(str gw-c1 " heads/dev"))
true)
(gitea-wire-test
"info/refs advertises tag"
(contains?
(dream-resp-body (gw-get "/alice/lib/info/refs" nil))
(str gw-tag-cid " tags/v1"))
true)
(gitea-wire-test
"info/refs advertises HEAD"
(contains?
(dream-resp-body (gw-get "/alice/lib/info/refs" nil))
"@ heads/main")
true)
(gitea-wire-test
"info/refs unknown repo 404"
(dream-status (gw-get "/alice/none/info/refs" nil))
404)
(define
gw-up-body
(fn
(wants haves)
(gitea/pkt-render
(list
(concat
(map (fn (c) (str "want " c)) wants)
(map (fn (c) (str "have " c)) haves))))))
(define
gw-up-lines
(fn
(resp)
(let
((s (gitea/pkt-sections (dream-resp-body resp))))
(if (empty? s) (list) (first s)))))
(gitea-wire-test
"upload-pack full clone size"
(len
(gw-up-lines
(gw-post
"/alice/lib/git-upload-pack"
nil
(gw-up-body (list gw-c2) (list)))))
9)
(gitea-wire-test
"upload-pack incremental size"
(len
(gw-up-lines
(gw-post
"/alice/lib/git-upload-pack"
nil
(gw-up-body (list gw-c2) (list gw-c1)))))
4)
(gitea-wire-test
"upload-pack lines all verify"
(len
(filter
(fn (l) (get (gitea/pack-line-parse l) :error))
(gw-up-lines
(gw-post
"/alice/lib/git-upload-pack"
nil
(gw-up-body (list gw-c2) (list))))))
0)
(gitea-wire-test
"receive-pack anon 401"
(dream-status
(gw-post
"/alice/lib/git-receive-pack"
nil
(gitea/pkt-render (list (list) (list)))))
401)
(gitea-wire-test
"receive-pack read-only 403"
(dream-status
(gw-post
"/alice/lib/git-receive-pack"
"tok-r"
(gitea/pkt-render (list (list) (list)))))
403)
; private repo wire gating
(gitea/repo-create! gw-forge "alice" "priv" {:visibility "private"})
(define gw-gp (gitea/repo-git gw-forge "alice" "priv"))
(git/add! gw-gp "p.txt" "private\n")
(git/commit! gw-gp {:message "p" :time 1 :author "alice"})
(gitea-wire-test
"private info/refs anon 404"
(dream-status (gw-get "/alice/priv/info/refs" nil))
404)
(gitea-wire-test
"private info/refs owner 200"
(dream-status (gw-get "/alice/priv/info/refs" "tok-a"))
200)
(gitea-wire-test
"private upload-pack anon 404"
(dream-status
(gw-post "/alice/priv/git-upload-pack" nil (gw-up-body (list) (list))))
404)
; ── client: ls-remote ────────────────────────────────────────────────
(define gw-remote-anon (gitea/remote gw-app "alice" "lib" nil))
(define gw-remote-bob (gitea/remote gw-app "alice" "lib" "tok-b"))
(define gw-remote-rope (gitea/remote gw-app "alice" "lib" "tok-r"))
(define gw-ls (gitea/ls-remote gw-remote-anon))
(gitea-wire-test "ls-remote head" (get gw-ls :head) "heads/main")
(gitea-wire-test "ls-remote main" (get (get gw-ls :refs) "heads/main") gw-c2)
(gitea-wire-test "ls-remote dev" (get (get gw-ls :refs) "heads/dev") gw-c1)
(gitea-wire-test
"ls-remote tag"
(get (get gw-ls :refs) "tags/v1")
gw-tag-cid)
(gitea-wire-test
"ls-remote unknown repo"
(gitea/ls-remote (gitea/remote gw-app "alice" "none" nil))
nil)
; ── client: clone into forge B ───────────────────────────────────────
(define gw-db2 (persist/mem-backend))
(define gw-forge2 (gitea/forge gw-db2))
(gitea/user-create! gw-forge2 "bob")
(define
gw-clone
(gitea/clone! gw-forge2 "bob" "lib" gw-remote-anon {}))
(gitea-wire-test "clone returns record" (get gw-clone :owner) "bob")
(define gw-gb (gitea/repo-git gw-forge2 "bob" "lib"))
(gitea-wire-test "clone main" (git/branch-get gw-gb "main") gw-c2)
(gitea-wire-test "clone dev" (git/branch-get gw-gb "dev") gw-c1)
(gitea-wire-test "clone tag" (git/tag-get gw-gb "v1") gw-tag-cid)
(gitea-wire-test "clone HEAD" (git/head-target gw-gb) "heads/main")
(gitea-wire-test
"clone default branch recorded"
(get (gitea/repo-get gw-forge2 "bob" "lib") :default-branch)
"main")
(gitea-wire-test "clone log" (git/log gw-gb gw-c2) (list gw-c2 gw-c1))
(gitea-wire-test
"clone blob content"
(git/blob-data
(git/read gw-gb (get (gitea/tree-at gw-gb gw-c2 "src/a.txt") :cid)))
"alpha2\n")
(gitea-wire-test
"clone closure complete"
(gitea/closure-complete? gw-gb (list gw-c2))
true)
(gitea-wire-test
"clone again conflicts"
(get
(gitea/clone! gw-forge2 "bob" "lib" gw-remote-anon {})
:conflict)
true)
(gitea-wire-test
"clone unreachable remote errors"
(get
(gitea/clone!
gw-forge2
"bob"
"ghost"
(gitea/remote gw-app "alice" "none" nil)
{})
:error)
404)
(gitea-wire-test
"failed clone leaves no repo"
(gitea/repo-exists? gw-forge2 "bob" "ghost")
false)
; ── client: fetch after upstream moves ───────────────────────────────
(git/add! gw-ga "src/c.txt" "gamma\n")
(define gw-c3 (git/commit! gw-ga {:message "third" :time 4 :author "alice"}))
(define gw-fetch1 (gitea/fetch! gw-remote-anon gw-gb))
(gitea-wire-test
"fetch stores new objects"
(> (get gw-fetch1 :stored) 0)
true)
(gitea-wire-test "fetch moves main" (git/branch-get gw-gb "main") gw-c3)
(gitea-wire-test
"fetch closure complete"
(gitea/closure-complete? gw-gb (list gw-c3))
true)
(gitea-wire-test
"fetch up-to-date is no-op"
(get (gitea/fetch! gw-remote-anon gw-gb) :stored)
0)
; ── client: push ─────────────────────────────────────────────────────
; bob gets write on the upstream, commits locally, pushes
(gitea/collab-add! gw-forge "alice" "lib" "bob" "write")
(git/checkout! gw-gb "main")
(git/add! gw-gb "src/b.txt" "beta\n")
(define gw-c4 (git/commit! gw-gb {:message "from-bob" :time 5 :author "bob"}))
(gitea-wire-test
"push ok"
(get (gitea/push! gw-remote-bob gw-gb "heads/main") :ok)
true)
(gitea-wire-test
"push moved upstream main"
(git/branch-get gw-ga "main")
gw-c4)
(gitea-wire-test
"upstream has pushed objects"
(gitea/closure-complete? gw-ga (list gw-c4))
true)
; push auth: anon 401, read-only 403
(gitea-wire-test
"push anon rejected"
(get (gitea/push! gw-remote-anon gw-gb "heads/main") :error)
401)
(gitea-wire-test
"push read-only rejected"
(get (gitea/push! gw-remote-rope gw-gb "heads/main") :error)
403)
; non-fast-forward: upstream moves on while bob commits on the old tip
(git/checkout! gw-ga "main")
(git/add! gw-ga "src/d.txt" "delta\n")
(define gw-c5 (git/commit! gw-ga {:message "upstream-moves" :time 6 :author "alice"}))
(git/add! gw-gb "src/e.txt" "eps\n")
(define gw-c4b (git/commit! gw-gb {:message "bob-diverges" :time 7 :author "bob"}))
(gitea-wire-test
"push non-fast-forward rejected"
(get (gitea/push! gw-remote-bob gw-gb "heads/main") :ng)
"non-fast-forward")
(gitea-wire-test
"upstream main unchanged after ng"
(git/branch-get gw-ga "main")
gw-c5)
; recover: fetch (mirror moves local main to upstream), rebuild, push
(gitea/fetch! gw-remote-bob gw-gb)
(gitea-wire-test
"fetch after ng syncs main"
(git/branch-get gw-gb "main")
gw-c5)
(git/checkout! gw-gb "main")
(git/add! gw-gb "src/e.txt" "eps\n")
(define gw-c6 (git/commit! gw-gb {:message "bob-rebased" :time 8 :author "bob"}))
(gitea-wire-test
"push after sync ok"
(get (gitea/push! gw-remote-bob gw-gb "heads/main") :ok)
true)
(gitea-wire-test
"upstream at bob's rebased tip"
(git/branch-get gw-ga "main")
gw-c6)
; branch create / delete over the wire
(git/branch-create! gw-gb "feat" gw-c6)
(gitea-wire-test
"push new branch"
(get (gitea/push! gw-remote-bob gw-gb "heads/feat") :ok)
true)
(gitea-wire-test
"upstream sees new branch"
(git/branch-get gw-ga "feat")
gw-c6)
(gitea-wire-test
"push delete branch"
(get (gitea/push-delete! gw-remote-bob gw-gb "heads/feat") :ok)
true)
(gitea-wire-test "upstream branch deleted" (git/branch-get gw-ga "feat") nil)
; tag push
(git/tag-lightweight! gw-gb "v2")
(gitea-wire-test
"push tag"
(get (gitea/push! gw-remote-bob gw-gb "tags/v2") :ok)
true)
(gitea-wire-test "upstream sees tag" (git/tag-get gw-ga "v2") gw-c6)
; guard rails
(gitea-wire-test
"push unknown local ref"
(get (gitea/push! gw-remote-bob gw-gb "heads/nope") :error)
"no-such-local-ref")
(gitea-wire-test
"apply-cmd rejects non-shared ref"
(gitea/apply-cmd! gw-ga {:name "bogus/x" :new gw-c1 :old "-"})
"ng bogus/x invalid-ref")
(gitea-wire-test
"apply-cmd rejects missing objects"
(gitea/apply-cmd! gw-ga {:name "heads/zzz" :new "sx1:missing" :old "-"})
"ng heads/zzz missing-objects")
; private repo full round trip with credentials
(define gw-remote-priv (gitea/remote gw-app "alice" "priv" "tok-a"))
(gitea/user-create! gw-forge2 "alice")
(define
gw-pclone
(gitea/clone! gw-forge2 "alice" "priv" gw-remote-priv {:visibility "private"}))
(gitea-wire-test "private clone with token" (get gw-pclone :owner) "alice")
(gitea-wire-test
"private clone anon fails"
(get
(gitea/clone!
gw-forge2
"alice"
"priv2"
(gitea/remote gw-app "alice" "priv" nil)
{})
:error)
404)
(gitea-wire-test
"pkt-sections malformed len errors"
(guard (e (true "errored")) (gitea/pkt-sections "zzzzzzzz"))
"errored")
(gitea-wire-test
"pkt-sections truncated pkt clamps"
(guard (e (true "errored")) (gitea/pkt-sections "0009hi"))
(list (list "hi")))
(define gw-po-blob (serialize (git/blob "hello \"quoted\" \n multiline")))
(define
gw-po-commit
(serialize (git/commit "sx1:tree" (list "sx1:p1" "sx1:p2") {:message "m" :author "a" :time 42})))
(gitea-wire-test
"parse-obj blob = sx-parse"
(gitea/parse-obj gw-po-blob)
(first (sx-parse gw-po-blob)))
(gitea-wire-test
"parse-obj commit = sx-parse"
(gitea/parse-obj gw-po-commit)
(first (sx-parse gw-po-commit)))
(gitea-wire-test
"parse-obj cid stable"
(git/cid (gitea/parse-obj gw-po-commit))
(git/cid (first (sx-parse gw-po-commit))))