hs-test: h2 client testing with h2spec 45/43445/4
authorMatus Fabian <[email protected]>
Thu, 17 Jul 2025 12:58:08 +0000 (08:58 -0400)
committerFlorin Coras <[email protected]>
Sun, 20 Jul 2025 20:05:41 +0000 (20:05 +0000)
Type: test

Change-Id: If588fa070adebb631bf71fc8c3e82a00ba628a69
Signed-off-by: Matus Fabian <[email protected]>
extras/hs-test/infra/hst_suite.go
extras/hs-test/infra/suite_http2.go

index b610b66..d7d9eb8 100644 (file)
@@ -93,6 +93,65 @@ var reservedPorts = []string{
        "6633",
        "6081",
        "53053",
+       // reserved for h2specd
+       "30000",
+       "30001",
+       "30002",
+       "30003",
+       "30004",
+       "30005",
+       "30006",
+       "30007",
+       "30008",
+       "30009",
+       "30010",
+       "30011",
+       "30012",
+       "30013",
+       "30014",
+       "30015",
+       "30016",
+       "30017",
+       "30018",
+       "30019",
+       "30020",
+       "30021",
+       "30022",
+       "30023",
+       "30024",
+       "30025",
+       "30026",
+       "30027",
+       "30028",
+       "30029",
+       "30030",
+       "30031",
+       "30032",
+       "30033",
+       "30034",
+       "30035",
+       "30036",
+       "30037",
+       "30038",
+       "30039",
+       "30040",
+       "30041",
+       "30042",
+       "30043",
+       "30044",
+       "30045",
+       "30046",
+       "30047",
+       "30048",
+       "30049",
+       "30050",
+       "30051",
+       "30052",
+       "30053",
+       "30054",
+       "30055",
+       "30056",
+       "30080",
 }
 
 // used for colorful ReportEntry
@@ -610,6 +669,7 @@ func (s *HstSuite) GeneratePort() string {
        }
        reservedPorts = append(reservedPorts, port)
        s.numOfNewPorts++
+       s.Log("generated port " + port)
        return port
 }
 
index d67399b..b26ffd9 100644 (file)
@@ -2,7 +2,9 @@ package hst
 
 import (
        "bytes"
+       "fmt"
        "io"
+       "net/http"
        "os"
        "reflect"
        "runtime"
@@ -13,6 +15,7 @@ import (
        "fd.io/hs-test/h2spec_extras"
        . "fd.io/hs-test/infra/common"
        . "github.com/onsi/ginkgo/v2"
+       "github.com/summerwind/h2spec"
        "github.com/summerwind/h2spec/config"
        "github.com/summerwind/h2spec/generic"
        "github.com/summerwind/h2spec/hpack"
@@ -24,6 +27,11 @@ var h2Tests = map[string][]func(s *Http2Suite){}
 var h2SoloTests = map[string][]func(s *Http2Suite){}
 var h2MWTests = map[string][]func(s *Http2Suite){}
 
+const (
+       h2specdFromPort   int = 30000
+       h2specdReportPort int = 30080
+)
+
 type Http2Suite struct {
        HstSuite
        Interfaces struct {
@@ -36,9 +44,10 @@ type Http2Suite struct {
                NginxServer *Container
        }
        Ports struct {
-               Port1      string
-               Port1AsInt int
-               Port2      string
+               Port1        string
+               Port1AsInt   int
+               Port2        string
+               H2specdAsInt int
        }
 }
 
@@ -396,7 +405,6 @@ var specs = []struct {
        {ExtrasTestGroup, extrasTests},
 }
 
-// Marked as pending since http plugin is not build with http/2 enabled by default
 var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() {
        var s Http2Suite
        BeforeAll(func() {
@@ -472,3 +480,163 @@ var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() {
                }
        }
 })
+
+func h2specdVerifyResult(s Http2Suite, nExecuted int) bool {
+       client := NewHttpClient(time.Second*5, false)
+       uri := fmt.Sprintf("http://%s:%d/report", s.HostAddr(), h2specdReportPort)
+       req, err := http.NewRequest("GET", uri, nil)
+       s.AssertNil(err, fmt.Sprint(err))
+       resp, err := client.Do(req)
+       s.AssertNil(err, fmt.Sprint(err))
+       defer resp.Body.Close()
+       report, err := io.ReadAll(resp.Body)
+       s.AssertContains(string(report), "0 failed")
+       expected := fmt.Sprintf("<div>%d tests, %d passed", nExecuted, nExecuted)
+       if !strings.Contains(string(report), expected) {
+               return false
+       }
+       return true
+}
+
+var _ = Describe("H2SpecClientSuite", Ordered, Serial, func() {
+       var s Http2Suite
+       BeforeAll(func() {
+               s.SetupSuite()
+       })
+       BeforeEach(func() {
+               s.SetupTest()
+       })
+       AfterAll(func() {
+               s.TeardownSuite()
+       })
+       AfterEach(func() {
+               s.TeardownTest()
+       })
+
+       testCases := []struct {
+               desc       string
+               portOffset int
+       }{
+               {desc: "client/1/1", portOffset: 0},
+               {desc: "client/4.1/1", portOffset: 1},
+               {desc: "client/4.1/2", portOffset: 2},
+               {desc: "client/4.1/3", portOffset: 3},
+               // TODO: message framing without content length using END_STREAM flag
+               //{desc: "client/4.2/1", portOffset: 4},
+               //{desc: "client/4.2/2", portOffset: 5},
+               {desc: "client/4.2/3", portOffset: 6},
+               {desc: "client/4.3/1", portOffset: 7},
+               {desc: "client/5.1/1", portOffset: 8},
+               {desc: "client/5.1/2", portOffset: 9},
+               {desc: "client/5.1/3", portOffset: 10},
+               {desc: "client/5.1/4", portOffset: 11},
+               // TODO: message framing without content length using END_STREAM flag
+               //{desc: "client/5.1/5", portOffset: 12},
+               //{desc: "client/5.1/6", portOffset: 13},
+               //{desc: "client/5.1/7", portOffset: 14},
+               {desc: "client/5.1/8", portOffset: 15},
+               {desc: "client/5.1/9", portOffset: 16},
+               {desc: "client/5.1/10", portOffset: 17},
+               {desc: "client/5.1.1/1", portOffset: 18},
+               {desc: "client/5.4.1/1", portOffset: 19},
+               {desc: "client/5.4.1/2", portOffset: 20},
+               {desc: "client/5.5/1", portOffset: 21},
+               {desc: "client/6.1/1", portOffset: 22},
+               {desc: "client/6.1/2", portOffset: 23},
+               {desc: "client/6.1/3", portOffset: 24},
+               {desc: "client/6.2/1", portOffset: 25},
+               {desc: "client/6.2/2", portOffset: 26},
+               {desc: "client/6.2/3", portOffset: 27},
+               // PRIORITY is deprecated
+               //{desc: "client/6.3/1", portOffset: 28},
+               //{desc: "client/6.3/2", portOffset: 29},
+               {desc: "client/6.4/1", portOffset: 30},
+               {desc: "client/6.4/2", portOffset: 31},
+               {desc: "client/6.4/3", portOffset: 32},
+               {desc: "client/6.5/1", portOffset: 33},
+               {desc: "client/6.5/2", portOffset: 34},
+               {desc: "client/6.5/3", portOffset: 35},
+               {desc: "client/6.5.2/1", portOffset: 36},
+               {desc: "client/6.5.2/2", portOffset: 37},
+               {desc: "client/6.5.2/3", portOffset: 38},
+               {desc: "client/6.5.2/4", portOffset: 39},
+               {desc: "client/6.5.3/1", portOffset: 40},
+               {desc: "client/6.7/1", portOffset: 41},
+               {desc: "client/6.7/2", portOffset: 42},
+               {desc: "client/6.7/3", portOffset: 43},
+               {desc: "client/6.7/4", portOffset: 44},
+               {desc: "client/6.8/1", portOffset: 45},
+               {desc: "client/6.9/1", portOffset: 46},
+               // TODO: message framing without content length using END_STREAM flag
+               //{desc: "client/6.9/2", portOffset: 47},
+               {desc: "client/6.9/3", portOffset: 48},
+               {desc: "client/6.9.1/1", portOffset: 49},
+               // TODO: message framing without content length using END_STREAM flag
+               //{desc: "client/6.9.1/2", portOffset: 50},
+               {desc: "client/6.10/1", portOffset: 51},
+               {desc: "client/6.10/2", portOffset: 52},
+               {desc: "client/6.10/3", portOffset: 53},
+               {desc: "client/6.10/4", portOffset: 54},
+               {desc: "client/6.10/5", portOffset: 55},
+               {desc: "client/6.10/6", portOffset: 56},
+       }
+
+       nExecuted := 0
+       for _, test := range testCases {
+               test := test
+               testName := "http2_test.go/h2spec_" + strings.ReplaceAll(test.desc, "/", "_")
+               It(testName, func(ctx SpecContext) {
+                       s.Log(testName + ": BEGIN")
+                       nExecuted++
+                       serverAddress := s.HostAddr()
+                       wd, _ := os.Getwd()
+                       conf := &config.Config{
+                               Host:         serverAddress,
+                               Port:         h2specdReportPort,
+                               Timeout:      20 * time.Second,
+                               MaxHeaderLen: 4096,
+                               TLS:          true,
+                               CertFile:     wd + "/resources/cert/localhost.crt",
+                               CertKeyFile:  wd + "/resources/cert/localhost.key",
+                               Verbose:      true,
+                               DryRun:       false,
+                               Exec:         "",
+                               FromPort:     h2specdFromPort,
+                               Sections:     []string{},
+                       }
+                       //capture h2spec output so it will be in log
+                       oldStdout := os.Stdout
+                       r, w, _ := os.Pipe()
+                       os.Stdout = w
+
+                       go h2spec.RunClientSpec(conf)
+
+                       cmd := fmt.Sprintf("http client timeout 5 verbose uri https://%s:%d/", serverAddress, h2specdFromPort+test.portOffset)
+                       res := s.Containers.Vpp.VppInstance.Vppctl(cmd)
+                       s.Log(res)
+                       s.AssertNotContains(res, "error: timeout")
+
+                       oChan := make(chan string)
+                       go func() {
+                               var buf bytes.Buffer
+                               io.Copy(&buf, r)
+                               oChan <- buf.String()
+                       }()
+
+                       //restore to normal state
+                       w.Close()
+                       os.Stdout = oldStdout
+                       o := <-oChan
+                       s.Log(o)
+
+                       //read report
+                       for nTries := 0; nTries < 30; nTries++ {
+                               if h2specdVerifyResult(s, nExecuted) {
+                                       break
+                               }
+                               time.Sleep(1 * time.Second)
+                       }
+
+               }, SpecTimeout(TestTimeout))
+       }
+})