tables_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // Copyright 2017 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package hpack
  5. import (
  6. "bufio"
  7. "regexp"
  8. "strconv"
  9. "strings"
  10. "testing"
  11. )
  12. func TestHeaderFieldTable(t *testing.T) {
  13. table := &headerFieldTable{}
  14. table.init()
  15. table.addEntry(pair("key1", "value1-1"))
  16. table.addEntry(pair("key2", "value2-1"))
  17. table.addEntry(pair("key1", "value1-2"))
  18. table.addEntry(pair("key3", "value3-1"))
  19. table.addEntry(pair("key4", "value4-1"))
  20. table.addEntry(pair("key2", "value2-2"))
  21. // Tests will be run twice: once before evicting anything, and
  22. // again after evicting the three oldest entries.
  23. tests := []struct {
  24. f HeaderField
  25. beforeWantStaticI uint64
  26. beforeWantMatch bool
  27. afterWantStaticI uint64
  28. afterWantMatch bool
  29. }{
  30. {HeaderField{"key1", "value1-1", false}, 1, true, 0, false},
  31. {HeaderField{"key1", "value1-2", false}, 3, true, 0, false},
  32. {HeaderField{"key1", "value1-3", false}, 3, false, 0, false},
  33. {HeaderField{"key2", "value2-1", false}, 2, true, 3, false},
  34. {HeaderField{"key2", "value2-2", false}, 6, true, 3, true},
  35. {HeaderField{"key2", "value2-3", false}, 6, false, 3, false},
  36. {HeaderField{"key4", "value4-1", false}, 5, true, 2, true},
  37. // Name match only, because sensitive.
  38. {HeaderField{"key4", "value4-1", true}, 5, false, 2, false},
  39. // Key not found.
  40. {HeaderField{"key5", "value5-x", false}, 0, false, 0, false},
  41. }
  42. staticToDynamic := func(i uint64) uint64 {
  43. if i == 0 {
  44. return 0
  45. }
  46. return uint64(table.len()) - i + 1 // dynamic is the reversed table
  47. }
  48. searchStatic := func(f HeaderField) (uint64, bool) {
  49. old := staticTable
  50. staticTable = table
  51. defer func() { staticTable = old }()
  52. return staticTable.search(f)
  53. }
  54. searchDynamic := func(f HeaderField) (uint64, bool) {
  55. return table.search(f)
  56. }
  57. for _, test := range tests {
  58. gotI, gotMatch := searchStatic(test.f)
  59. if wantI, wantMatch := test.beforeWantStaticI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
  60. t.Errorf("before evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
  61. }
  62. gotI, gotMatch = searchDynamic(test.f)
  63. wantDynamicI := staticToDynamic(test.beforeWantStaticI)
  64. if wantI, wantMatch := wantDynamicI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
  65. t.Errorf("before evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
  66. }
  67. }
  68. table.evictOldest(3)
  69. for _, test := range tests {
  70. gotI, gotMatch := searchStatic(test.f)
  71. if wantI, wantMatch := test.afterWantStaticI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
  72. t.Errorf("after evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
  73. }
  74. gotI, gotMatch = searchDynamic(test.f)
  75. wantDynamicI := staticToDynamic(test.afterWantStaticI)
  76. if wantI, wantMatch := wantDynamicI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
  77. t.Errorf("after evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
  78. }
  79. }
  80. }
  81. func TestHeaderFieldTable_LookupMapEviction(t *testing.T) {
  82. table := &headerFieldTable{}
  83. table.init()
  84. table.addEntry(pair("key1", "value1-1"))
  85. table.addEntry(pair("key2", "value2-1"))
  86. table.addEntry(pair("key1", "value1-2"))
  87. table.addEntry(pair("key3", "value3-1"))
  88. table.addEntry(pair("key4", "value4-1"))
  89. table.addEntry(pair("key2", "value2-2"))
  90. // evict all pairs
  91. table.evictOldest(table.len())
  92. if l := table.len(); l > 0 {
  93. t.Errorf("table.len() = %d, want 0", l)
  94. }
  95. if l := len(table.byName); l > 0 {
  96. t.Errorf("len(table.byName) = %d, want 0", l)
  97. }
  98. if l := len(table.byNameValue); l > 0 {
  99. t.Errorf("len(table.byNameValue) = %d, want 0", l)
  100. }
  101. }
  102. func TestStaticTable(t *testing.T) {
  103. fromSpec := `
  104. +-------+-----------------------------+---------------+
  105. | 1 | :authority | |
  106. | 2 | :method | GET |
  107. | 3 | :method | POST |
  108. | 4 | :path | / |
  109. | 5 | :path | /index.html |
  110. | 6 | :scheme | http |
  111. | 7 | :scheme | https |
  112. | 8 | :status | 200 |
  113. | 9 | :status | 204 |
  114. | 10 | :status | 206 |
  115. | 11 | :status | 304 |
  116. | 12 | :status | 400 |
  117. | 13 | :status | 404 |
  118. | 14 | :status | 500 |
  119. | 15 | accept-charset | |
  120. | 16 | accept-encoding | gzip, deflate |
  121. | 17 | accept-language | |
  122. | 18 | accept-ranges | |
  123. | 19 | accept | |
  124. | 20 | access-control-allow-origin | |
  125. | 21 | age | |
  126. | 22 | allow | |
  127. | 23 | authorization | |
  128. | 24 | cache-control | |
  129. | 25 | content-disposition | |
  130. | 26 | content-encoding | |
  131. | 27 | content-language | |
  132. | 28 | content-length | |
  133. | 29 | content-location | |
  134. | 30 | content-range | |
  135. | 31 | content-type | |
  136. | 32 | cookie | |
  137. | 33 | date | |
  138. | 34 | etag | |
  139. | 35 | expect | |
  140. | 36 | expires | |
  141. | 37 | from | |
  142. | 38 | host | |
  143. | 39 | if-match | |
  144. | 40 | if-modified-since | |
  145. | 41 | if-none-match | |
  146. | 42 | if-range | |
  147. | 43 | if-unmodified-since | |
  148. | 44 | last-modified | |
  149. | 45 | link | |
  150. | 46 | location | |
  151. | 47 | max-forwards | |
  152. | 48 | proxy-authenticate | |
  153. | 49 | proxy-authorization | |
  154. | 50 | range | |
  155. | 51 | referer | |
  156. | 52 | refresh | |
  157. | 53 | retry-after | |
  158. | 54 | server | |
  159. | 55 | set-cookie | |
  160. | 56 | strict-transport-security | |
  161. | 57 | transfer-encoding | |
  162. | 58 | user-agent | |
  163. | 59 | vary | |
  164. | 60 | via | |
  165. | 61 | www-authenticate | |
  166. +-------+-----------------------------+---------------+
  167. `
  168. bs := bufio.NewScanner(strings.NewReader(fromSpec))
  169. re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
  170. for bs.Scan() {
  171. l := bs.Text()
  172. if !strings.Contains(l, "|") {
  173. continue
  174. }
  175. m := re.FindStringSubmatch(l)
  176. if m == nil {
  177. continue
  178. }
  179. i, err := strconv.Atoi(m[1])
  180. if err != nil {
  181. t.Errorf("Bogus integer on line %q", l)
  182. continue
  183. }
  184. if i < 1 || i > staticTable.len() {
  185. t.Errorf("Bogus index %d on line %q", i, l)
  186. continue
  187. }
  188. if got, want := staticTable.ents[i-1].Name, m[2]; got != want {
  189. t.Errorf("header index %d name = %q; want %q", i, got, want)
  190. }
  191. if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
  192. t.Errorf("header index %d value = %q; want %q", i, got, want)
  193. }
  194. }
  195. if err := bs.Err(); err != nil {
  196. t.Error(err)
  197. }
  198. }