mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 20:52:22 +00:00
215 lines
8.6 KiB
Go
215 lines
8.6 KiB
Go
// Copyright 2017 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package hpack
|
|
|
|
import (
|
|
"bufio"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestHeaderFieldTable(t *testing.T) {
|
|
table := &headerFieldTable{}
|
|
table.init()
|
|
table.addEntry(pair("key1", "value1-1"))
|
|
table.addEntry(pair("key2", "value2-1"))
|
|
table.addEntry(pair("key1", "value1-2"))
|
|
table.addEntry(pair("key3", "value3-1"))
|
|
table.addEntry(pair("key4", "value4-1"))
|
|
table.addEntry(pair("key2", "value2-2"))
|
|
|
|
// Tests will be run twice: once before evicting anything, and
|
|
// again after evicting the three oldest entries.
|
|
tests := []struct {
|
|
f HeaderField
|
|
beforeWantStaticI uint64
|
|
beforeWantMatch bool
|
|
afterWantStaticI uint64
|
|
afterWantMatch bool
|
|
}{
|
|
{HeaderField{"key1", "value1-1", false}, 1, true, 0, false},
|
|
{HeaderField{"key1", "value1-2", false}, 3, true, 0, false},
|
|
{HeaderField{"key1", "value1-3", false}, 3, false, 0, false},
|
|
{HeaderField{"key2", "value2-1", false}, 2, true, 3, false},
|
|
{HeaderField{"key2", "value2-2", false}, 6, true, 3, true},
|
|
{HeaderField{"key2", "value2-3", false}, 6, false, 3, false},
|
|
{HeaderField{"key4", "value4-1", false}, 5, true, 2, true},
|
|
// Name match only, because sensitive.
|
|
{HeaderField{"key4", "value4-1", true}, 5, false, 2, false},
|
|
// Key not found.
|
|
{HeaderField{"key5", "value5-x", false}, 0, false, 0, false},
|
|
}
|
|
|
|
staticToDynamic := func(i uint64) uint64 {
|
|
if i == 0 {
|
|
return 0
|
|
}
|
|
return uint64(table.len()) - i + 1 // dynamic is the reversed table
|
|
}
|
|
|
|
searchStatic := func(f HeaderField) (uint64, bool) {
|
|
old := staticTable
|
|
staticTable = table
|
|
defer func() { staticTable = old }()
|
|
return staticTable.search(f)
|
|
}
|
|
|
|
searchDynamic := func(f HeaderField) (uint64, bool) {
|
|
return table.search(f)
|
|
}
|
|
|
|
for _, test := range tests {
|
|
gotI, gotMatch := searchStatic(test.f)
|
|
if wantI, wantMatch := test.beforeWantStaticI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
|
|
t.Errorf("before evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
|
}
|
|
gotI, gotMatch = searchDynamic(test.f)
|
|
wantDynamicI := staticToDynamic(test.beforeWantStaticI)
|
|
if wantI, wantMatch := wantDynamicI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
|
|
t.Errorf("before evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
|
}
|
|
}
|
|
|
|
table.evictOldest(3)
|
|
|
|
for _, test := range tests {
|
|
gotI, gotMatch := searchStatic(test.f)
|
|
if wantI, wantMatch := test.afterWantStaticI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
|
|
t.Errorf("after evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
|
}
|
|
gotI, gotMatch = searchDynamic(test.f)
|
|
wantDynamicI := staticToDynamic(test.afterWantStaticI)
|
|
if wantI, wantMatch := wantDynamicI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
|
|
t.Errorf("after evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHeaderFieldTable_LookupMapEviction(t *testing.T) {
|
|
table := &headerFieldTable{}
|
|
table.init()
|
|
table.addEntry(pair("key1", "value1-1"))
|
|
table.addEntry(pair("key2", "value2-1"))
|
|
table.addEntry(pair("key1", "value1-2"))
|
|
table.addEntry(pair("key3", "value3-1"))
|
|
table.addEntry(pair("key4", "value4-1"))
|
|
table.addEntry(pair("key2", "value2-2"))
|
|
|
|
// evict all pairs
|
|
table.evictOldest(table.len())
|
|
|
|
if l := table.len(); l > 0 {
|
|
t.Errorf("table.len() = %d, want 0", l)
|
|
}
|
|
|
|
if l := len(table.byName); l > 0 {
|
|
t.Errorf("len(table.byName) = %d, want 0", l)
|
|
}
|
|
|
|
if l := len(table.byNameValue); l > 0 {
|
|
t.Errorf("len(table.byNameValue) = %d, want 0", l)
|
|
}
|
|
}
|
|
|
|
func TestStaticTable(t *testing.T) {
|
|
fromSpec := `
|
|
+-------+-----------------------------+---------------+
|
|
| 1 | :authority | |
|
|
| 2 | :method | GET |
|
|
| 3 | :method | POST |
|
|
| 4 | :path | / |
|
|
| 5 | :path | /index.html |
|
|
| 6 | :scheme | http |
|
|
| 7 | :scheme | https |
|
|
| 8 | :status | 200 |
|
|
| 9 | :status | 204 |
|
|
| 10 | :status | 206 |
|
|
| 11 | :status | 304 |
|
|
| 12 | :status | 400 |
|
|
| 13 | :status | 404 |
|
|
| 14 | :status | 500 |
|
|
| 15 | accept-charset | |
|
|
| 16 | accept-encoding | gzip, deflate |
|
|
| 17 | accept-language | |
|
|
| 18 | accept-ranges | |
|
|
| 19 | accept | |
|
|
| 20 | access-control-allow-origin | |
|
|
| 21 | age | |
|
|
| 22 | allow | |
|
|
| 23 | authorization | |
|
|
| 24 | cache-control | |
|
|
| 25 | content-disposition | |
|
|
| 26 | content-encoding | |
|
|
| 27 | content-language | |
|
|
| 28 | content-length | |
|
|
| 29 | content-location | |
|
|
| 30 | content-range | |
|
|
| 31 | content-type | |
|
|
| 32 | cookie | |
|
|
| 33 | date | |
|
|
| 34 | etag | |
|
|
| 35 | expect | |
|
|
| 36 | expires | |
|
|
| 37 | from | |
|
|
| 38 | host | |
|
|
| 39 | if-match | |
|
|
| 40 | if-modified-since | |
|
|
| 41 | if-none-match | |
|
|
| 42 | if-range | |
|
|
| 43 | if-unmodified-since | |
|
|
| 44 | last-modified | |
|
|
| 45 | link | |
|
|
| 46 | location | |
|
|
| 47 | max-forwards | |
|
|
| 48 | proxy-authenticate | |
|
|
| 49 | proxy-authorization | |
|
|
| 50 | range | |
|
|
| 51 | referer | |
|
|
| 52 | refresh | |
|
|
| 53 | retry-after | |
|
|
| 54 | server | |
|
|
| 55 | set-cookie | |
|
|
| 56 | strict-transport-security | |
|
|
| 57 | transfer-encoding | |
|
|
| 58 | user-agent | |
|
|
| 59 | vary | |
|
|
| 60 | via | |
|
|
| 61 | www-authenticate | |
|
|
+-------+-----------------------------+---------------+
|
|
`
|
|
bs := bufio.NewScanner(strings.NewReader(fromSpec))
|
|
re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
|
|
for bs.Scan() {
|
|
l := bs.Text()
|
|
if !strings.Contains(l, "|") {
|
|
continue
|
|
}
|
|
m := re.FindStringSubmatch(l)
|
|
if m == nil {
|
|
continue
|
|
}
|
|
i, err := strconv.Atoi(m[1])
|
|
if err != nil {
|
|
t.Errorf("Bogus integer on line %q", l)
|
|
continue
|
|
}
|
|
if i < 1 || i > staticTable.len() {
|
|
t.Errorf("Bogus index %d on line %q", i, l)
|
|
continue
|
|
}
|
|
if got, want := staticTable.ents[i-1].Name, m[2]; got != want {
|
|
t.Errorf("header index %d name = %q; want %q", i, got, want)
|
|
}
|
|
if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
|
|
t.Errorf("header index %d value = %q; want %q", i, got, want)
|
|
}
|
|
}
|
|
if err := bs.Err(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|