AtlatestRepositorysigil-websocket

sigil-websocket / tree / testtest-websocket.sgl

1(import (sigil test)
2 (sigil websocket connection)
3 (sigil websocket frame))
4
5;; ============================================================
6;; Frame opcodes
7;; ============================================================
8
9(test-group "frame opcodes"
10 (test "opcode values"
11 (assert-equal #x0 opcode-continuation)
12 (assert-equal #x1 opcode-text)
13 (assert-equal #x2 opcode-binary)
14 (assert-equal #x8 opcode-close)
15 (assert-equal #x9 opcode-ping)
16 (assert-equal #xA opcode-pong))
18 (test "opcode-control? identifies control frames"
19 (assert-false (opcode-control? opcode-text))
20 (assert-false (opcode-control? opcode-binary))
21 (assert-true (opcode-control? opcode-close))
22 (assert-true (opcode-control? opcode-ping))
23 (assert-true (opcode-control? opcode-pong))))
25;; ============================================================
26;; Frame encoding
27;; ============================================================
29(test-group "frame encoding"
30 (test "encode-text-frame produces bytevector"
31 (let ((frame (encode-text-frame "hello")))
32 (assert-true (bytevector? frame))
33 (assert-true (> (bytevector-length frame) 5))))
35 (test "encode-ping-frame produces bytevector"
36 (let ((frame (encode-ping-frame)))
37 (assert-true (bytevector? frame))))
39 (test "encode-close-frame produces bytevector"
40 (let ((frame (encode-close-frame)))
41 (assert-true (bytevector? frame)))))
43;; ============================================================
44;; Frame encode/decode roundtrip
45;; ============================================================
47(test-group "frame roundtrip"
48 (test "decode from encode-text-frame"
49 ;; encode-text-frame generates a masked frame for client use
50 (let* ((encoded (encode-text-frame "hello"))
51 (result (decode-frame encoded)))
52 (assert-true (frame-decode-result? result))
53 (let ((frame (frame-decode-result-frame result)))
54 (when frame
55 (assert-equal opcode-text (ws-frame-opcode frame)))))))
57;; ============================================================
58;; Masking
59;; ============================================================
61(test-group "masking"
62 (test "generate-mask-key returns 4 bytes"
63 (let ((key (generate-mask-key)))
64 (assert-true (bytevector? key))
65 (assert-equal 4 (bytevector-length key))))
67 (test "apply-mask is reversible"
68 (let* ((key (generate-mask-key))
69 (data (make-bytevector 5))
70 (masked (apply-mask data key))
71 (unmasked (apply-mask masked key)))
72 (assert-equal data unmasked)))
74 (test "masking changes data"
75 (let* ((key (generate-mask-key))
76 (data (make-bytevector 16))
77 (masked (apply-mask data key)))
78 ;; Masked should differ (unless mask key is all zeros, extremely unlikely)
79 (assert-false (equal? data masked)))))
81;; ============================================================
82;; ws-receive-nowait
83;;
84;; Every connection below is built with socket = #f ON PURPOSE. If
85;; `ws-receive-nowait` ever touched the socket these tests would raise
86;; rather than pass, so "it does not consume socket bytes" is enforced by
87;; construction instead of asserted in a comment.
88;; ============================================================
90;; `define-struct` constructors are KEYWORD-based, not positional. The first
91;; draft of these tests used positional arguments and would have failed with
92;; `keyword->symbol: expected keyword` — caught by running the shape in a
93;; scratch script, because `sigil test` cannot run in this repo at all (see the
94;; note on the closed toolchain mismatch below).
95(define (open-conn-with buffer)
96 (ws-connection socket: #f state: 'open url: "ws://test/x" host: "test"
97 buffer: buffer fragments: '()))
99(define (two-frames-one-segment)
100 (bytevector-append (encode-server-text-frame "FIRST")
101 (encode-server-text-frame "SECOND")))
103(test-group "ws-receive-nowait"
104 (test "drains a SECOND frame coalesced into one segment"
105 ;; This is the defect in miniature: one socket read yielded two complete
106 ;; frames, so after the first is returned the second is buffered with
107 ;; nothing further on the socket. Measured against 0.10.4 over a real
108 ;; socket, that second frame surfaced 2997ms late — only when unrelated
109 ;; bytes arrived.
110 (let* ((conn (open-conn-with (two-frames-one-segment)))
111 (m1 (ws-receive-nowait conn))
112 (m2 (ws-receive-nowait conn))
113 (m3 (ws-receive-nowait conn)))
114 (assert-true (ws-message? m1))
115 (assert-equal "FIRST" (ws-message-data m1))
116 (assert-true (ws-message? m2))
117 (assert-equal "SECOND" (ws-message-data m2))
118 ;; and the buffer is now genuinely empty, not merely quiet
119 (assert-false m3)))
121 (test "returns #f cleanly on an empty buffer — no block, no error"
122 (assert-false (ws-receive-nowait (open-conn-with (make-bytevector 0)))))
124 (test "a PARTIAL frame is #f, not an error, and is left in the buffer"
125 ;; 'need-more must not be reported as failure: the caller's next socket
126 ;; read completes the frame. If this returned an error the caller would
127 ;; tear down a healthy connection mid-frame.
128 (let* ((whole (encode-server-text-frame "PARTIAL-ME"))
129 (head (bytevector-copy whole 0 3))
130 (conn (open-conn-with head)))
131 (assert-false (ws-receive-nowait conn))
132 (assert-equal 3 (bytevector-length (ws-connection-buffer conn)))))
134 (test "a NOT-YET-PROMOTED connection is #f, NOT 'closed"
135 ;; The regression that matters most here. `ws-connect` does not promote the
136 ;; state to 'open until a receive poll has run, so mirroring `ws-receive`'s
137 ;; "anything not connected is 'closed" reports a FRESH socket as dead. A
138 ;; non-blocking caller sees that window immediately and tears down a
139 ;; healthy connection; `ws-receive` hides it only because it blocks after.
140 (let ((conn (ws-connection socket: #f state: 'connecting url: "ws://test/x"
141 host: "test" buffer: (make-bytevector 0)
142 fragments: '())))
143 (assert-false (ws-receive-nowait conn))))
145 (test "reports 'closed on a closed connection"
146 (let ((conn (ws-connection socket: #f state: 'closed url: "ws://test/x"
147 host: "test" buffer: (make-bytevector 0)
148 fragments: '())))
149 (assert-equal 'closed (ws-receive-nowait conn)))))
151(run-tests)