3 # Test HTTP/1.1 concurrent requests including
4 # queueing, pipelining and retries.
6 # Copyright (C) 2018 Keith Nash <kjnash@users.sourceforge.net>
8 # See the file "license.terms" for information on usage and redistribution
9 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 if {"::tcltest" ni [namespace children]} {
12 package require tcltest 2.5
13 namespace import -force ::tcltest::*
16 package require http 2.9
18 set sourcedir [file normalize [file dirname [info script]]]
19 source [file join $sourcedir httpTest.tcl]
20 source [file join $sourcedir httpTestScript.tcl]
22 # ------------------------------------------------------------------------------
23 # (1) Define the test scripts that will be used to generate logs for analysis -
24 # and also define the "correct" results.
25 # ------------------------------------------------------------------------------
27 proc ReturnTestScriptAndResult {ca cb delay te} {
55 return -code error {no matching script}
71 set resShort {1 ? ? ?}
83 set resShort {1 ? ? ? ?}
84 set resLong {1 2 3 4 5}
96 set resShort {1 ? ? ? ? ?}
97 set resLong {1 2 3 4 5 6}
105 POST b address=home code=brief paid=yes
112 set resShort {1 ? ? ? 5 ? ? ? ?}
113 set resLong {1 2 3 4 5 6 7 8 9}
117 POST a address=home code=brief paid=yes
118 POST b address=home code=brief paid=yes
119 POST c address=home code=brief paid=yes
120 POST a address=home code=brief paid=yes
121 POST b address=home code=brief paid=yes
122 POST c address=home code=brief paid=yes
123 POST a address=home code=brief paid=yes
124 POST b address=home code=brief paid=yes
125 POST c address=home code=brief paid=yes
128 set resShort {1 2 3 4 5 6 7 8 9}
129 set resLong {1 2 3 4 5 6 7 8 9}
133 POST a address=home code=brief paid=yes
134 GET b address=home code=brief paid=yes
135 POST c address=home code=brief paid=yes
136 GET a address=home code=brief paid=yes
137 GET b address=home code=brief paid=yes
138 POST c address=home code=brief paid=yes
139 POST a address=home code=brief paid=yes
140 HEAD b address=home code=brief paid=yes
141 GET c address=home code=brief paid=yes
144 set resShort {1 ? 3 ? ? 6 7 ? ?}
145 set resLong {1 2 3 4 5 6 7 8 9}
149 GET b address=home code=brief paid=yes
150 POST a address=home code=brief paid=yes
151 GET a address=home code=brief paid=yes
152 POST c address=home code=brief paid=yes
153 GET b address=home code=brief paid=yes
154 HEAD b address=home code=brief paid=yes
155 POST c address=home code=brief paid=yes
156 POST a address=home code=brief paid=yes
157 GET c address=home code=brief paid=yes
160 set resShort {1 2 ? 4 ? ? 7 8 ?}
161 set resLong {1 2 3 4 5 6 7 8 9}
165 # Telling the server to close the connection.
177 set resShort {1 ? 3 ? ? ? ? ? ?}
178 set resLong {1 2 3 4 5 6 7 8 9}
182 # Telling the server to close the connection.
184 POST b close=y address=home code=brief paid=yes
194 set resShort {1 2 3 ? ? ? ? ? ?}
195 set resLong {1 2 3 4 5 6 7 8 9}
199 # Telling the server to close the connection.
202 POST c address=home code=brief paid=yes
211 set resShort {1 ? 3 ? ? ? ? ? ?}
212 set resLong {1 2 3 4 5 6 7 8 9}
216 # Telling the server to close the connection twice.
228 set resShort {1 ? 3 ? ? 6 ? ? ?}
229 set resLong {1 2 3 4 5 6 7 8 9}
233 # Telling the server to delay before sending the response.
241 set resShort {1 ? ? ? ?}
242 set resLong {1 2 3 4 5}
246 # Making the server close the connection (time out).
255 set resShort {1 2 ? ? ?}
256 set resLong {1 2 3 4 5}
260 # Making the server close the connection (time out) twice.
274 set resShort {1 2 ? ? 5 ? ? ? ?}
275 set resLong {1 2 3 4 5 6 7 8 9}
279 POST a address=home code=brief paid=yes
280 POST b address=home code=brief paid=yes close=y delay=1
281 POST c address=home code=brief paid=yes delay=1
282 POST a address=home code=brief paid=yes close=y
284 POST b address=home code=brief paid=yes delay=1
285 POST c address=home code=brief paid=yes close=y
286 POST a address=home code=brief paid=yes
287 POST b address=home code=brief paid=yes close=y
288 POST c address=home code=brief paid=yes
291 set resShort {1 2 3 4 5 6 7 8 9}
292 set resLong {1 2 3 4 5 6 7 8 9}
296 POST a address=home code=brief paid=yes
297 GET b address=home code=brief paid=yes
298 POST c address=home code=brief paid=yes close=y
299 GET a address=home code=brief paid=yes
300 GET b address=home code=brief paid=yes close=y
301 POST c address=home code=brief paid=yes
303 POST a address=home code=brief paid=yes
304 HEAD b address=home code=brief paid=yes close=y
305 GET c address=home code=brief paid=yes
308 set resShort {1 ? 3 4 ? 6 7 ? 9}
309 set resLong {1 2 3 4 5 6 7 8 9}
313 GET b address=home code=brief paid=yes
314 POST a address=home code=brief paid=yes
315 GET a address=home code=brief paid=yes
316 POST c address=home code=brief paid=yes close=y
317 GET b address=home code=brief paid=yes
318 HEAD b address=home code=brief paid=yes close=y
319 POST c address=home code=brief paid=yes
321 POST a address=home code=brief paid=yes
323 GET c address=home code=brief paid=yes
326 set resShort {1 2 3 4 5 ? 7 8 9}
327 set resLong {1 2 3 4 5 6 7 8 9}
335 POST b address=home code=brief paid=yes
340 set resShort {1 2 ? ?}
341 set resLong {1 2 3 4}
342 # resShort is overwritten below for the case ($te == 1).
350 GET b address=home code=brief paid=yes
355 set resShort {1 2 ? ?}
356 set resLong {1 2 3 4}
364 POST b address=home code=brief paid=yes
378 GET b address=home code=brief paid=yes
383 set resShort {1 2 ? ?}
384 set resLong {1 2 3 4}
391 POST b address=home code=brief paid=yes
406 GET b address=home code=brief paid=yes
419 POST b address=home code=brief paid=yes
433 GET b address=home code=brief paid=yes
444 return -code error {no matching script}
451 set result "Passed all sanity checks."
453 } elseif {$ca == 3} {
454 # Keep-Alive, not pipelined.
456 append result "Passed all sanity checks.\n"
457 append result "Have overlaps including response body:\n"
460 # Keep-Alive, pipelined: ($ca == 4)
462 append result "Passed all sanity checks.\n"
463 append result "Overlap-free without response body:\n"
464 append result "$resShort"
467 # - The special case of test *.18*-testEof needs test results to be
468 # individually written.
469 # - These test -repost 0 when there is a POST to apply it to, and the server
470 # timeout has not been detected.
471 if {($cb == 18) && ($te == 1)} {
474 set result "Passed all sanity checks."
476 } elseif {$ca == 3 && $delay == 0} {
477 # Keep-Alive, not pipelined.
478 set result [MakeMessage {
479 |Problems with sanity checks:
480 |Wrong sequence for token ::http::2 - {A B C D X X X}
482 |Wrong sequence for token ::http::3 - {A X X}
484 |Wrong sequence for token ::http::4 - {A X X X}
487 |Have overlaps including response body:
491 } elseif {$ca == 3} {
492 # Keep-Alive, not pipelined.
493 set result [MakeMessage {
494 |Problems with sanity checks:
495 |Wrong sequence for token ::http::2 - {A B C D X X X}
498 |Have overlaps including response body:
502 } elseif {$delay == 0} {
503 # Keep-Alive, pipelined: ($ca == 4)
504 set result [MakeMessage {
505 |Problems with sanity checks:
506 |Wrong sequence for token ::http::2 - {A B C D X X X}
508 |Wrong sequence for token ::http::3 - {A X X}
510 |Wrong sequence for token ::http::4 - {A X X X}
513 |Overlap-free without response body:
518 set result [MakeMessage {
519 |Problems with sanity checks:
520 |Wrong sequence for token ::http::2 - {A B C D X X X}
523 |Overlap-free without response body:
530 return [list "$start$middle$end" $result]
533 # ------------------------------------------------------------------------------
535 # ------------------------------------------------------------------------------
536 # WHD's one-line command to generate multi-line strings from readable code.
539 # set blurb [MakeMessage {
540 # |This command allows multi-line strings to be created with readable
541 # |code, and without breaking the rules for indentation.
543 # |The command shifts the entire block of text to the left, omitting
544 # |the pipe character and the spaces to its left.
546 # ------------------------------------------------------------------------------
548 proc MakeMessage {in} {
549 regsub -all -line {^\s*\|} [string trim $in] {}
550 # N.B. Implicit Return.
554 proc ReturnTestScript {ca cb delay te} {
555 lassign [ReturnTestScriptAndResult $ca $cb $delay $te] script result
559 proc ReturnTestResult {ca cb delay te} {
560 lassign [ReturnTestScriptAndResult $ca $cb $delay $te] script result
565 # ------------------------------------------------------------------------------
566 # (2) Command to run a test script and use httpTest to analyse the logs.
567 # ------------------------------------------------------------------------------
569 namespace import httpTestScript::runHttpTestScript
570 namespace import httpTestScript::cleanupHttpTestScript
571 namespace import httpTest::cleanupHttpTest
572 namespace import httpTest::logAnalyse
573 namespace import httpTest::setHttpTestOptions
575 proc RunTest {header footer delay te} {
576 set num [runHttpTestScript [ReturnTestScript $header $footer $delay $te]]
581 # --------------------------------------------------------------------------
582 # Custom code for specific tests
583 # --------------------------------------------------------------------------
586 for {set i 1} {$i <= $num} {incr i} {
589 } elseif {$header > 2 && $footer == 18 && $te == 1} {
592 # Transaction 1 is conventional.
593 # Check that transactions 2,3,4 are cancelled.
595 set notIncluded $notPiped
597 # Transaction 1 is conventional.
598 # Check that transaction 2 is cancelled.
599 # The timing of transactions 3 and 4 is uncertain.
601 set notIncluded $notPiped
603 } elseif {$footer in {20 22 23 24 25}} {
604 # Transaction 2 uses its own socket.
606 set notIncluded $notPiped
609 # --------------------------------------------------------------------------
610 # End of custom code for specific tests
611 # --------------------------------------------------------------------------
614 set Results [logAnalyse $num $skipOverlaps $notIncluded $notPiped]
615 lassign $Results msg cleanE cleanF dirtyE dirtyF
617 set msg "Passed all sanity checks."
619 set msg "Problems with sanity checks:\n$msg"
624 puts "Overlap-free including response body:\n$cleanF"
625 puts "Have overlaps including response body:\n$dirtyF"
626 puts "Overlap-free without response body:\n$cleanE"
627 puts "Have overlaps without response body:\n$dirtyE"
631 # No ordering, just check that transactions all finish
633 } elseif {$header == 3} {
634 # Not pipelined - check overlaps with response body.
635 set result "$msg\nHave overlaps including response body:\n$dirtyF"
637 # Pipelined - check overlaps without response body. Check that the
638 # first request, the first requests after replay, and POSTs are clean.
639 set result "$msg\nOverlap-free without response body:\n$cleanE"
646 # ------------------------------------------------------------------------------
647 # (3) VERBOSITY CONTROL
648 # ------------------------------------------------------------------------------
649 # If tests fail, run an individual test with -verbose 1 or 2 for diagnosis.
650 # If still obscure, uncomment #Log and ##Log lines in the http package.
651 # ------------------------------------------------------------------------------
653 setHttpTestOptions -verbose 0
655 # ------------------------------------------------------------------------------
656 # (4) Define the base URLs used for testing. Each must have a query string.
657 # ------------------------------------------------------------------------------
658 # - A HTTP/1.1 server is required. It should be configured to provide
659 # persistent connections when requested to do so, and to close these
660 # connections if they are idle for one second.
661 # - The resource must be served with status 200 in response to a valid GET or
663 # - The value of "page" is always specified in the query-string. Different
664 # resources for the three values of "page" allow testing of both chunked and
665 # unchunked transfer encoding.
666 # - The variables "close" and "delay" may be specified in the query-string (for
667 # a GET) or the request body (for a POST).
668 # - "delay" is a numerical value in seconds, and causes the server to delay
669 # the response, including headers.
670 # - "close", if it has the value "y", instructs the server to close the
671 # connection ater the current request.
672 # - Any other variables should be ignored.
673 # ------------------------------------------------------------------------------
675 namespace eval ::httpTestScript {
678 a http://test-tcl-http.kerlin.org/index.html?page=privacy
679 b http://test-tcl-http.kerlin.org/index.html?page=conditions
680 c http://test-tcl-http.kerlin.org/index.html?page=welcome
685 # ------------------------------------------------------------------------------
686 # (5) Define the tests
687 # ------------------------------------------------------------------------------
689 # - serverNeeded - the URLs defined at (4) must be available, and must have the
690 # properties specified there.
691 # - duplicate - the value of -pipeline does not matter if -keepalive 0
692 # - timeout1s - tests that work correctly only if the server closes
693 # persistent connections after one second.
695 # Server timeout of persistent connections should be 1s. Delays of 2s are
696 # intended to cause timeout.
697 # Servers are usually configured to use a longer timeout: this will cause the
698 # tests to fail. The "2000" could be replaced with a larger number, but the
699 # tests will then be inconveniently slow.
700 # ------------------------------------------------------------------------------
702 #testConstraint serverNeeded 1
703 #testConstraint timeout1s 1
704 #testConstraint duplicate 1
706 # ------------------------------------------------------------------------------
707 # Proc SetTestEof - to edit the command ::http::KeepSocket
708 # ------------------------------------------------------------------------------
709 # The usual line in command ::http::KeepSocket is " set TEST_EOF 0".
710 # Whether the value set in the file is 0 or 1, change it here to the value
711 # specified by the argument.
713 # It is worth doing all tests for both values of the argument.
715 # test 0 - ::http::KeepSocket is unchanged, detects server eof where possible
716 # and closes the connection.
717 # test 1 - ::http::KeepSocket is edited, does not detect server eof, so the
718 # reaction to finding server eof can be tested without the difficulty
719 # of testing in the few milliseconds of an asynchronous close event.
720 # ------------------------------------------------------------------------------
722 proc SetTestEof {test} {
723 set body [info body ::http::KeepSocket]
724 set subs " set TEST_EOF $test"
725 set count [regsub -line -all -- {^\s*set TEST_EOF .*$} $body $subs newBody]
727 return -code error {proc ::http::KeepSocket has unexpected form}
729 proc ::http::KeepSocket {token} $newBody
733 for {set header 1} {$header <= 4} {incr header} {
735 setHttpTestOptions -dotted 1
738 setHttpTestOptions -dotted 0
743 set cons0 {serverNeeded duplicate}
745 set cons0 serverNeeded
748 for {set footer 1} {$footer <= 25} {incr footer} {
749 foreach {delay label} {
770 # ------------------------------------------------------------------
771 # Custom code for individual tests
772 # ------------------------------------------------------------------
773 if {$footer in {18}} {
775 if {($label eq "j") && ($te == 1)} {
779 # The test (of REPOST 0) is useful if tag is "testEof"
780 # (server timeout without client reaction). The same test
781 # has a different result if tag is "normal".
783 set suffix " - extra test for -repost 0 - ::http::2 must be"
784 append suffix " cancelled"
786 append suffix ", along with ::http::3 ::http::4 if"
787 append suffix " the test creates these before ::http::2"
788 append suffix " is cancelled"
792 } elseif {$footer in {19}} {
793 set suffix " - extra test for -repost 0"
794 } elseif {$footer in {20 21}} {
795 set suffix " - extra test for -postfresh 1"
796 if {($footer == 20)} {
797 append suffix " - ::http::2 uses a separate socket"
798 append suffix ", other requests use a persistent connection"
800 } elseif {$footer in {22 23 24 25}} {
801 append suffix " - ::http::2 uses a separate socket"
802 append suffix ", other requests use a persistent connection"
806 if {($footer >= 13 && $footer <= 23)} {
807 # Test use WAIT and depend on server timeout before this time.
808 lappend cons timeout1s
810 # ------------------------------------------------------------------
811 # End of custom code.
812 # ------------------------------------------------------------------
814 set name "pipeline test header $header footer $footer delay $delay $tag$suffix"
818 test httpPipeline-${header}.${footer}${label}-${tag} $name \
820 -setup [string map [list TE $te] {
821 # Restore default values for tests:
822 http::config -pipeline 1 -postfresh 0 -repost 1
824 set http::http(uid) 0
826 }] -body [list RunTest $header $footer $delay $te] -cleanup {
827 # Restore default values for tests:
828 http::config -pipeline 1 -postfresh 0 -repost 1
829 cleanupHttpTestScript
833 # Wait for persistent sockets on the server to time out.
834 } -result [ReturnTestResult $header $footer $delay $te] -match $match
843 # ------------------------------------------------------------------------------
844 # (*) Notes on tests *.18*-testEof, *.19*-testEof - these test -repost 0
845 # ------------------------------------------------------------------------------
846 # These tests are a bit awkward because the main test kit analyses whether all
847 # requests are satisfied, with retries if necessary, and it has result analysis
848 # for processing retry logs.
849 # - *.18*-testEof tests that certain requests are NOT satisfied, so the analysis
851 # - Tests *.18a-testEof depend on client/server timing - the test needs to call
852 # http::geturl for all requests before the POST (request 2) is cancelled.
853 # We test that requests 2, 3, 4 are all cancelled.
854 # - Other tests *.18*-testEof may not request 3 and 4 in time for the to be
855 # added to the write queue before request 2 is completed. We simply check that
856 # request 2 is cancelled.
857 # - The behaviour is different if all connections are allowed to time out
858 # (label "j"). This case is not needed to test -repost 0, and is omitted.
859 # - Tests *.18*-normal and *.19* are conventional (-repost 0 should have no
861 # ------------------------------------------------------------------------------
864 unset header footer delay label suffix match cons name te
865 namespace delete ::httpTest
866 namespace delete ::httpTestScript
868 ::tcltest::cleanupTests