aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorCaleb Maclennan <caleb@alerque.com>2026-08-31 10:57:58 +0300
committerGitHub <noreply@github.com>2026-08-31 10:57:58 +0300
commit8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266 (patch)
treec11ae97c461d7f9c3cfe84773b9975de48d5ba9c /test
parent535178a3f0e2cff59f4d59ee3a655bee263a5c90 (diff)
parent4863f32b358a8d533f629084a86fb4932ebbd2f3 (diff)
downloadluasocket-8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266.tar.gz
luasocket-8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266.tar.bz2
luasocket-8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266.zip
Merge pull request #463 from lunarmodules/feat/receive-limit
feat(receive): add maxsize argument to bound memory usage
Diffstat (limited to 'test')
-rw-r--r--test/testclnt.lua195
-rw-r--r--test/utestclnt.lua45
2 files changed, 240 insertions, 0 deletions
diff --git a/test/testclnt.lua b/test/testclnt.lua
index 170e187..bbe48bb 100644
--- a/test/testclnt.lua
+++ b/test/testclnt.lua
@@ -619,6 +619,198 @@ remote([[
619end 619end
620 620
621------------------------------------------------------------------------ 621------------------------------------------------------------------------
622function test_maxsize()
623 -- Group A: argument errors, raised before any I/O
624 reconnect()
625 printf("argument errors: ")
626 -- bounds pre-implementation calls (where arg 4 is silently dropped and
627 -- these become real, otherwise-unbounded blocking reads on an idle
628 -- socket) so a meaningful failure doesn't hang the suite
629 data:settimeout(0.2)
630 local ok
631 ok = pcall(data.receive, data, "*l", nil, 0)
632 assert(not ok, "A1 failed: maxsize=0 should raise")
633 ok = pcall(data.receive, data, "*l", nil, -1)
634 assert(not ok, "A2 failed: maxsize=-1 should raise")
635 ok = pcall(data.receive, data, "*l", nil, "abc")
636 assert(not ok, "A3 failed: non-number maxsize should raise")
637 ok = pcall(data.receive, data, "*l", string.rep("x", 10), 10)
638 assert(not ok, "A4 failed: #prefix == maxsize should raise")
639 ok = pcall(data.receive, data, "*l", string.rep("x", 11), 10)
640 assert(not ok, "A5 failed: #prefix > maxsize should raise")
641 ok = pcall(data.receive, data, 100, nil, 50)
642 assert(not ok, "A6 failed: wanted > maxsize should raise")
643 ok = pcall(data.receive, data, "*l", nil, math.huge)
644 assert(not ok, "A7 failed: maxsize=math.huge should raise (size_t overflow)")
645 ok = pcall(data.receive, data, math.huge)
646 assert(not ok, "A8 failed: wanted=math.huge should raise (size_t overflow)")
647 data:settimeout(0.1)
648 ok = pcall(data.receive, data, 50, nil, 100)
649 assert(ok, "A9 failed: wanted <= maxsize should not raise")
650 data:settimeout(-1)
651 remote [[ data:send('intact\n') ]]
652 local line, err = data:receive("*l", nil, 100)
653 assert(line == "intact",
654 "A10 failed: socket touched by a failed argcheck (err=" ..
655 tostring(err) .. ")")
656 pass("ok")
657
658 -- Group B: *l boundary
659 reconnect()
660 printf("*l boundary: ")
661 remote [[ data:send('hello\n') ]]
662 local d, e, p = data:receive("*l", nil, 100)
663 assert(d == "hello" and e == nil, "B1 failed")
664
665 remote(string.format([[data:send(string.rep('a',%d) .. '\n')]], 100))
666 d, e = data:receive("*l", nil, 100)
667 assert(d == string.rep("a", 100) and e == nil,
668 "B2 failed: exact-budget line should succeed")
669
670 remote(string.format([[data:send(string.rep('a',%d) .. '\n')]], 101))
671 d, e, p = data:receive("*l", nil, 100)
672 assert(d == nil and e == "oversized" and p == string.rep("a", 100),
673 "B3 failed: one-over-budget should be oversized")
674 d, e = data:receive("*l", nil, 100)
675 assert(d == "a" and e == nil,
676 "B3 failed: leftover byte and terminator should still be there")
677
678 remote(string.format([[data:send(string.rep('\r',%d) .. string.rep('a',%d) .. '\n')]], 50, 100))
679 d, e = data:receive("*l", nil, 100)
680 assert(d == string.rep("a", 100) and e == nil, "B4 failed: CRs must not count")
681
682 remote(string.format([[data:send(string.rep('a',%d) .. '\r\n')]], 100))
683 d, e = data:receive("*l", nil, 100)
684 assert(d == string.rep("a", 100) and e == nil, "B5 failed: CRLF at boundary")
685 pass("ok")
686
687 remote(string.format([[data:send(string.rep('a',%d) .. '\n')]], 101))
688 d, e, p = data:receive("*l", nil, 100)
689 assert(d == nil and e == "oversized" and p == string.rep("a", 100),
690 "B6 failed: one-over-budget should be oversized")
691 d, e = data:receive("*l", p, 150) -- increase limit, try again with prefix
692 assert(d == string.rep("a", 101) and e == nil,
693 "B6 failed: leftover byte and terminator should still be there")
694
695 -- Group C: *l and I1 (timeout/close at the cap)
696 reconnect()
697 printf("I1 (timeout/closed at the cap): ")
698 remote(string.format([[data:send(string.rep('a',%d))]], 50))
699 data:settimeout(0.5)
700 d, e, p = data:receive("*l", nil, 100)
701 assert(d == nil and e == "timeout", "C1 failed: expected timeout below cap")
702 assert(#p < 100, "C1 failed: timeout partial must be strictly < maxsize")
703 ok = pcall(data.receive, data, "*l", p, 100)
704 assert(ok, "C1 failed: retry with prefix=partial, same maxsize must not raise")
705
706 reconnect()
707 remote(string.format([[data:send(string.rep('a',%d))]], 100))
708 data:settimeout(0.5)
709 d, e, p = data:receive("*l", nil, 100)
710 assert(e == "oversized" and #p == 100,
711 "C2 failed: exactly-at-cap timeout must be oversized, got " .. tostring(e))
712
713 reconnect()
714 remote(string.format([[data:send(string.rep('a',%d)) data:close() data = nil]], 100))
715 d, e, p = data:receive("*l", nil, 100)
716 assert(e == "oversized" and #p == 100,
717 "C3 failed: exactly-at-cap close must be oversized, got " .. tostring(e))
718 pass("ok")
719
720 -- Group D: drain idiom
721 -- 5030 (not a multiple of the 100 cap) so the boundary of the 50th
722 -- oversized chunk doesn't land exactly on the '\n': when it does, the
723 -- terminator check wins over the cap check (by design -- see B2/I1) and
724 -- the would-be 50th oversized chunk instead succeeds outright.
725 reconnect()
726 printf("drain idiom: ")
727 remote(string.format([[data:send(string.rep('x',%d) .. '\n' .. 'next\n')]], 5030))
728 local iterations = 0
729 repeat
730 d, e, p = data:receive("*l", "", 100)
731 if e == "oversized" then
732 assert(#p == 100, "D1 failed: oversized partial length " .. #p)
733 iterations = iterations + 1
734 end
735 until e ~= "oversized"
736 assert(iterations == 50,
737 "D1 failed: expected 50 oversized iterations, got " .. iterations)
738 assert(d == string.rep("x", 30) and e == nil, "D1 failed: final call should succeed")
739 local nextline = data:receive("*l")
740 assert(nextline == "next", "D1 failed: stream misaligned, got " .. tostring(nextline))
741 pass("ok")
742
743 -- Group E: *a
744 reconnect()
745 printf("*a boundary: ")
746 remote [[ data:send('abc') data:close() data = nil ]]
747 d, e = data:receive("*a", nil, 100)
748 assert(d == "abc" and e == nil, "E1 failed")
749
750 reconnect()
751 remote(string.format([[data:send(string.rep('a',%d)) data:close() data = nil]], 100))
752 d, e = data:receive("*a", nil, 100)
753 assert(d == string.rep("a", 100) and e == nil,
754 "E2 failed: completion should beat the cap")
755
756 reconnect()
757 remote(string.format([[data:send(string.rep('a',%d)) data:close() data = nil]], 250))
758 d, e, p = data:receive("*a", "", 100)
759 assert(e == "oversized" and #p == 100, "E3a failed: first chunk")
760 d, e, p = data:receive("*a", "", 100)
761 assert(e == "oversized" and #p == 100, "E3a failed: second chunk")
762 d, e = data:receive("*a", "", 100)
763 assert(d == string.rep("a", 50) and e == nil, "E3a failed: final chunk")
764
765 reconnect()
766 remote(string.format([[data:send(string.rep('a',%d)) data:close() data = nil]], 250))
767 d, e, p = data:receive("*a", "", 100)
768 assert(e == "oversized" and #p == 100, "E3b failed: first chunk")
769 d, e, p = data:receive("*a", p, 200) -- increase maxsize, try again with prefix
770 assert(e == "oversized" and #p == 200, "E3b failed: second chunk")
771 d, e = data:receive("*a", p, 300) -- increase maxsize, try again with prefix
772 assert(d == string.rep("a", 250) and e == nil, "E3b failed: final chunk")
773
774 reconnect()
775 remote(string.format([[data:send(string.rep('a',%d))]], 100))
776 data:settimeout(0.3)
777 d, e, p = data:receive("*a", nil, 100)
778 assert(e == "oversized" and #p == 100,
779 "E4 failed: expected oversized not timeout, got " .. tostring(e))
780
781 reconnect()
782 remote(string.format([[data:send(string.rep('a',%d))]], 50))
783 data:settimeout(0.3)
784 d, e, p = data:receive("*a", nil, 100)
785 assert(e == "timeout" and #p < 100, "E5 failed")
786 pass("ok")
787
788 -- Group F: numeric pattern (recvraw untouched)
789 reconnect()
790 printf("numeric pattern (recvraw unchanged): ")
791 remote(string.format([[data:send(string.rep('a',%d))]], 50))
792 d, e = data:receive(50, nil, 100)
793 assert(d == string.rep("a", 50) and e == nil, "F1 failed")
794
795 reconnect()
796 remote(string.format([[data:send(string.rep('a',%d))]], 25))
797 d, e = data:receive(50, string.rep("p", 25), 100)
798 assert(e == nil and #d == 50, "F2 failed")
799 pass("ok")
800
801 -- Group G: no-maxsize regression snapshot
802 reconnect()
803 printf("no-maxsize regression: ")
804 remote(string.format([[data:send(string.rep('a',%d) .. '\n')]], 5000))
805 d, e = data:receive("*l")
806 assert(d == string.rep("a", 5000) and e == nil, "G2 failed: plain *l")
807 remote(string.format([[data:send(string.rep('a',%d) .. '\n')]], 5000))
808 d, e = data:receive("*l", "")
809 assert(d == string.rep("a", 5000) and e == nil, "G2 failed: *l with prefix")
810 pass("ok")
811end
812
813------------------------------------------------------------------------
622test("method registration") 814test("method registration")
623 815
624local tcp_methods = { 816local tcp_methods = {
@@ -796,6 +988,9 @@ test_blockingtimeoutreceive(800091, 2, 3)
796test_blockingtimeoutreceive(800091, 3, 2) 988test_blockingtimeoutreceive(800091, 3, 2)
797test_blockingtimeoutreceive(800091, 3, 1) 989test_blockingtimeoutreceive(800091, 3, 1)
798 990
991test("receive maxsize")
992test_maxsize()
993
799test("shutting server down") 994test("shutting server down")
800reconnect() 995reconnect()
801remote("os.exit()") 996remote("os.exit()")
diff --git a/test/utestclnt.lua b/test/utestclnt.lua
index 7f10643..395260c 100644
--- a/test/utestclnt.lua
+++ b/test/utestclnt.lua
@@ -511,6 +511,48 @@ remote(string.format([[
511end 511end
512 512
513------------------------------------------------------------------------ 513------------------------------------------------------------------------
514function test_maxsize()
515 -- A4: #prefix == maxsize raises (mirrors testclnt.lua group A)
516 reconnect()
517 pass("argument errors")
518 -- bounds the pre-implementation call (arg 4 silently dropped, so this
519 -- becomes a real blocking read on an idle socket) so a meaningful
520 -- failure doesn't hang the suite
521 data:settimeout(0.2)
522 local ok = pcall(data.receive, data, "*l", string.rep("x", 10), 10)
523 assert(not ok, "A4 failed: #prefix == maxsize should raise")
524 data:settimeout(-1)
525
526 -- B2/B3: *l boundary (mirrors testclnt.lua group B)
527 pass("*l boundary")
528 remote(string.format([[data:send(string.rep('a',%d) .. '\n')]], 100))
529 local d, e, p = data:receive("*l", nil, 100)
530 assert(d == string.rep("a", 100) and e == nil, "B2 failed")
531
532 remote(string.format([[data:send(string.rep('a',%d) .. '\n')]], 101))
533 d, e, p = data:receive("*l", nil, 100)
534 assert(d == nil and e == "oversized" and p == string.rep("a", 100), "B3 failed")
535 d, e = data:receive("*l", nil, 100)
536 assert(d == "a" and e == nil, "B3 failed: leftover byte")
537
538 -- C2: timeout exactly at the cap is oversized, not timeout (I1)
539 reconnect()
540 remote(string.format([[data:send(string.rep('a',%d))]], 100))
541 data:settimeout(0.5)
542 d, e, p = data:receive("*l", nil, 100)
543 assert(e == "oversized" and #p == 100,
544 "C2 failed: expected oversized, got " .. tostring(e))
545
546 -- E2: completion beats the cap (I2)
547 reconnect()
548 remote(string.format([[data:send(string.rep('a',%d)) data:close() data = nil]], 100))
549 d, e = data:receive("*a", nil, 100)
550 assert(d == string.rep("a", 100) and e == nil,
551 "E2 failed: completion should beat the cap")
552 pass("ok")
553end
554
555------------------------------------------------------------------------
514 556
515test("method registration") 557test("method registration")
516test_methods(socket.unix(), { 558test_methods(socket.unix(), {
@@ -641,4 +683,7 @@ test_blockingtimeoutreceive(800091, 2, 3)
641test_blockingtimeoutreceive(800091, 3, 2) 683test_blockingtimeoutreceive(800091, 3, 2)
642test_blockingtimeoutreceive(800091, 3, 1) 684test_blockingtimeoutreceive(800091, 3, 1)
643 685
686test("receive maxsize")
687test_maxsize()
688
644test(string.format("done in %.2fs", socket.gettime() - start)) 689test(string.format("done in %.2fs", socket.gettime() - start))