github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/stree/stree_test.go (about) 1 // Copyright 2023-2024 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package stree 15 16 import ( 17 crand "crypto/rand" 18 "encoding/hex" 19 "flag" 20 "fmt" 21 "math/rand" 22 "strings" 23 "testing" 24 "time" 25 "unsafe" 26 ) 27 28 // Print Results: go test -v --args --results 29 // For some benchmarks. 30 var runResults = flag.Bool("results", false, "Enable Results Tests") 31 32 func TestSubjectTreeBasics(t *testing.T) { 33 st := NewSubjectTree[int]() 34 require_Equal(t, st.Size(), 0) 35 // Single leaf 36 old, updated := st.Insert(b("foo.bar.baz"), 22) 37 require_True(t, old == nil) 38 require_False(t, updated) 39 require_Equal(t, st.Size(), 1) 40 // Find with single leaf. 41 v, found := st.Find(b("foo.bar.baz")) 42 require_True(t, found) 43 require_Equal(t, *v, 22) 44 // Update single leaf 45 old, updated = st.Insert(b("foo.bar.baz"), 33) 46 require_True(t, old != nil) 47 require_Equal(t, *old, 22) 48 require_True(t, updated) 49 require_Equal(t, st.Size(), 1) 50 // Split the tree 51 old, updated = st.Insert(b("foo.bar"), 22) 52 require_True(t, old == nil) 53 require_False(t, updated) 54 require_Equal(t, st.Size(), 2) 55 // Now we have node4 -> leaf*2 56 v, found = st.Find(b("foo.bar")) 57 require_True(t, found) 58 require_Equal(t, *v, 22) 59 // Make sure we can still retrieve the original after the split. 60 v, found = st.Find(b("foo.bar.baz")) 61 require_True(t, found) 62 require_Equal(t, *v, 33) 63 } 64 65 func TestSubjectTreeNodeGrow(t *testing.T) { 66 st := NewSubjectTree[int]() 67 for i := 0; i < 4; i++ { 68 subj := b(fmt.Sprintf("foo.bar.%c", 'A'+i)) 69 old, updated := st.Insert(subj, 22) 70 require_True(t, old == nil) 71 require_False(t, updated) 72 } 73 // We have filled a node4. 74 _, ok := st.root.(*node4) 75 require_True(t, ok) 76 // This one will trigger us to grow. 77 old, updated := st.Insert(b("foo.bar.E"), 22) 78 require_True(t, old == nil) 79 require_False(t, updated) 80 _, ok = st.root.(*node16) 81 require_True(t, ok) 82 // We do not have node48, so once we fill this we should jump to node256. 83 for i := 5; i < 16; i++ { 84 subj := b(fmt.Sprintf("foo.bar.%c", 'A'+i)) 85 old, updated := st.Insert(subj, 22) 86 require_True(t, old == nil) 87 require_False(t, updated) 88 } 89 // This one will trigger us to grow. 90 old, updated = st.Insert(b("foo.bar.Q"), 22) 91 require_True(t, old == nil) 92 require_False(t, updated) 93 _, ok = st.root.(*node256) 94 require_True(t, ok) 95 } 96 97 func TestSubjectTreeNodePrefixMismatch(t *testing.T) { 98 st := NewSubjectTree[int]() 99 st.Insert(b("foo.bar.A"), 11) 100 st.Insert(b("foo.bar.B"), 22) 101 st.Insert(b("foo.bar.C"), 33) 102 // Grab current root. Split below will cause update. 103 or := st.root 104 // This one will force a split of the node 105 st.Insert(b("foo.foo.A"), 44) 106 require_True(t, or != st.root) 107 // Now make sure we can retrieve correctly. 108 v, found := st.Find(b("foo.bar.A")) 109 require_True(t, found) 110 require_Equal(t, *v, 11) 111 v, found = st.Find(b("foo.bar.B")) 112 require_True(t, found) 113 require_Equal(t, *v, 22) 114 v, found = st.Find(b("foo.bar.C")) 115 require_True(t, found) 116 require_Equal(t, *v, 33) 117 v, found = st.Find(b("foo.foo.A")) 118 require_True(t, found) 119 require_Equal(t, *v, 44) 120 } 121 122 func TestSubjectTreeNodeDelete(t *testing.T) { 123 st := NewSubjectTree[int]() 124 st.Insert(b("foo.bar.A"), 22) 125 v, found := st.Delete(b("foo.bar.A")) 126 require_True(t, found) 127 require_Equal(t, *v, 22) 128 require_Equal(t, st.root, nil) 129 v, found = st.Delete(b("foo.bar.A")) 130 require_False(t, found) 131 require_Equal(t, v, nil) 132 v, found = st.Find(b("foo.foo.A")) 133 require_False(t, found) 134 require_Equal(t, v, nil) 135 // Kick to a node4. 136 st.Insert(b("foo.bar.A"), 11) 137 st.Insert(b("foo.bar.B"), 22) 138 st.Insert(b("foo.bar.C"), 33) 139 // Make sure we can delete and that we shrink back to leaf. 140 v, found = st.Delete(b("foo.bar.C")) 141 require_True(t, found) 142 require_Equal(t, *v, 33) 143 v, found = st.Delete(b("foo.bar.B")) 144 require_True(t, found) 145 require_Equal(t, *v, 22) 146 // We should have shrunk here. 147 require_True(t, st.root.isLeaf()) 148 v, found = st.Delete(b("foo.bar.A")) 149 require_True(t, found) 150 require_Equal(t, *v, 11) 151 require_Equal(t, st.root, nil) 152 // Now pop up to a node16 and make sure we can shrink back down. 153 for i := 0; i < 5; i++ { 154 subj := fmt.Sprintf("foo.bar.%c", 'A'+i) 155 st.Insert(b(subj), 22) 156 } 157 _, ok := st.root.(*node16) 158 require_True(t, ok) 159 v, found = st.Delete(b("foo.bar.A")) 160 require_True(t, found) 161 require_Equal(t, *v, 22) 162 _, ok = st.root.(*node4) 163 require_True(t, ok) 164 // Now pop up to node256 165 st = NewSubjectTree[int]() 166 for i := 0; i < 17; i++ { 167 subj := fmt.Sprintf("foo.bar.%c", 'A'+i) 168 st.Insert(b(subj), 22) 169 } 170 _, ok = st.root.(*node256) 171 require_True(t, ok) 172 v, found = st.Delete(b("foo.bar.A")) 173 require_True(t, found) 174 require_Equal(t, *v, 22) 175 _, ok = st.root.(*node16) 176 require_True(t, ok) 177 v, found = st.Find(b("foo.bar.B")) 178 require_True(t, found) 179 require_Equal(t, *v, 22) 180 } 181 182 func TestSubjectTreeNodesAndPaths(t *testing.T) { 183 st := NewSubjectTree[int]() 184 check := func(subj string) { 185 t.Helper() 186 v, found := st.Find(b(subj)) 187 require_True(t, found) 188 require_Equal(t, *v, 22) 189 } 190 st.Insert(b("foo.bar.A"), 22) 191 st.Insert(b("foo.bar.B"), 22) 192 st.Insert(b("foo.bar.C"), 22) 193 st.Insert(b("foo.bar"), 22) 194 check("foo.bar.A") 195 check("foo.bar.B") 196 check("foo.bar.C") 197 check("foo.bar") 198 // This will do several things in terms of shrinking and pruning, 199 // want to make sure it gets prefix correct for new top node4. 200 st.Delete(b("foo.bar")) 201 check("foo.bar.A") 202 check("foo.bar.B") 203 check("foo.bar.C") 204 } 205 206 // Check that we are constructing a proper tree with complex insert patterns. 207 func TestSubjectTreeConstruction(t *testing.T) { 208 st := NewSubjectTree[int]() 209 st.Insert(b("foo.bar.A"), 1) 210 st.Insert(b("foo.bar.B"), 2) 211 st.Insert(b("foo.bar.C"), 3) 212 st.Insert(b("foo.baz.A"), 11) 213 st.Insert(b("foo.baz.B"), 22) 214 st.Insert(b("foo.baz.C"), 33) 215 st.Insert(b("foo.bar"), 42) 216 217 checkNode := func(an *node, kind string, pors string, numChildren uint16) { 218 // t.Helper() 219 require_True(t, an != nil) 220 n := *an 221 require_True(t, n != nil) 222 require_Equal(t, n.kind(), kind) 223 require_Equal(t, pors, string(n.path())) 224 require_Equal(t, numChildren, n.numChildren()) 225 } 226 227 checkNode(&st.root, "NODE4", "foo.ba", 2) 228 nn := st.root.findChild('r') 229 checkNode(nn, "NODE4", "r", 2) 230 checkNode((*nn).findChild(0), "LEAF", "", 0) 231 rnn := (*nn).findChild('.') 232 checkNode(rnn, "NODE4", ".", 3) 233 checkNode((*rnn).findChild('A'), "LEAF", "A", 0) 234 checkNode((*rnn).findChild('B'), "LEAF", "B", 0) 235 checkNode((*rnn).findChild('C'), "LEAF", "C", 0) 236 znn := st.root.findChild('z') 237 checkNode(znn, "NODE4", "z.", 3) 238 checkNode((*znn).findChild('A'), "LEAF", "A", 0) 239 checkNode((*znn).findChild('B'), "LEAF", "B", 0) 240 checkNode((*znn).findChild('C'), "LEAF", "C", 0) 241 // Use st.Dump() if you want a tree print out. 242 243 // Now delete "foo.bar" and make sure put ourselves back together properly. 244 v, found := st.Delete(b("foo.bar")) 245 require_True(t, found) 246 require_Equal(t, *v, 42) 247 248 checkNode(&st.root, "NODE4", "foo.ba", 2) 249 nn = st.root.findChild('r') 250 checkNode(nn, "NODE4", "r.", 3) 251 checkNode((*nn).findChild('A'), "LEAF", "A", 0) 252 checkNode((*nn).findChild('B'), "LEAF", "B", 0) 253 checkNode((*nn).findChild('C'), "LEAF", "C", 0) 254 znn = st.root.findChild('z') 255 checkNode(znn, "NODE4", "z.", 3) 256 checkNode((*znn).findChild('A'), "LEAF", "A", 0) 257 checkNode((*znn).findChild('B'), "LEAF", "B", 0) 258 checkNode((*znn).findChild('C'), "LEAF", "C", 0) 259 } 260 261 func match(t *testing.T, st *SubjectTree[int], filter string, expected int) { 262 t.Helper() 263 var matches []int 264 st.Match(b(filter), func(_ []byte, v *int) { 265 matches = append(matches, *v) 266 }) 267 require_Equal(t, expected, len(matches)) 268 } 269 270 func TestSubjectTreeMatchLeafOnly(t *testing.T) { 271 st := NewSubjectTree[int]() 272 st.Insert(b("foo.bar.baz.A"), 1) 273 274 // Check all placements of pwc in token space. 275 match(t, st, "foo.bar.*.A", 1) 276 match(t, st, "foo.*.baz.A", 1) 277 match(t, st, "foo.*.*.A", 1) 278 match(t, st, "foo.*.*.*", 1) 279 match(t, st, "*.*.*.*", 1) 280 // Now check fwc. 281 match(t, st, ">", 1) 282 match(t, st, "foo.>", 1) 283 match(t, st, "foo.*.>", 1) 284 match(t, st, "foo.bar.>", 1) 285 match(t, st, "foo.bar.*.>", 1) 286 287 // Check partials so they do not trigger on leafs. 288 match(t, st, "foo.bar.baz", 0) 289 } 290 291 func TestSubjectTreeMatchNodes(t *testing.T) { 292 st := NewSubjectTree[int]() 293 st.Insert(b("foo.bar.A"), 1) 294 st.Insert(b("foo.bar.B"), 2) 295 st.Insert(b("foo.bar.C"), 3) 296 st.Insert(b("foo.baz.A"), 11) 297 st.Insert(b("foo.baz.B"), 22) 298 st.Insert(b("foo.baz.C"), 33) 299 300 // Test literals. 301 match(t, st, "foo.bar.A", 1) 302 match(t, st, "foo.baz.A", 1) 303 match(t, st, "foo.bar", 0) 304 // Test internal pwc 305 match(t, st, "foo.*.A", 2) 306 // Test terminal pwc 307 match(t, st, "foo.bar.*", 3) 308 match(t, st, "foo.baz.*", 3) 309 // Check fwc 310 match(t, st, ">", 6) 311 match(t, st, "foo.>", 6) 312 match(t, st, "foo.bar.>", 3) 313 match(t, st, "foo.baz.>", 3) 314 // Make sure we do not have false positives on prefix matches. 315 match(t, st, "foo.ba", 0) 316 317 // Now add in "foo.bar" to make a more complex tree construction 318 // and re-test. 319 st.Insert(b("foo.bar"), 42) 320 321 // Test literals. 322 match(t, st, "foo.bar.A", 1) 323 match(t, st, "foo.baz.A", 1) 324 match(t, st, "foo.bar", 1) 325 // Test internal pwc 326 match(t, st, "foo.*.A", 2) 327 // Test terminal pwc 328 match(t, st, "foo.bar.*", 3) 329 match(t, st, "foo.baz.*", 3) 330 // Check fwc 331 match(t, st, ">", 7) 332 match(t, st, "foo.>", 7) 333 match(t, st, "foo.bar.>", 3) 334 match(t, st, "foo.baz.>", 3) 335 } 336 337 func TestSubjectTreeNoPrefix(t *testing.T) { 338 st := NewSubjectTree[int]() 339 for i := 0; i < 26; i++ { 340 subj := b(fmt.Sprintf("%c", 'A'+i)) 341 old, updated := st.Insert(subj, 22) 342 require_True(t, old == nil) 343 require_False(t, updated) 344 } 345 n, ok := st.root.(*node256) 346 require_True(t, ok) 347 require_Equal(t, n.numChildren(), 26) 348 v, found := st.Delete(b("B")) 349 require_True(t, found) 350 require_Equal(t, *v, 22) 351 require_Equal(t, n.numChildren(), 25) 352 v, found = st.Delete(b("Z")) 353 require_True(t, found) 354 require_Equal(t, *v, 22) 355 require_Equal(t, n.numChildren(), 24) 356 } 357 358 func TestSubjectTreePartialTerminalWildcardBugMatch(t *testing.T) { 359 st := NewSubjectTree[int]() 360 st.Insert(b("STATE.GLOBAL.CELL1.7PDSGAALXNN000010.PROPERTY-A"), 5) 361 st.Insert(b("STATE.GLOBAL.CELL1.7PDSGAALXNN000010.PROPERTY-B"), 1) 362 st.Insert(b("STATE.GLOBAL.CELL1.7PDSGAALXNN000010.PROPERTY-C"), 2) 363 match(t, st, "STATE.GLOBAL.CELL1.7PDSGAALXNN000010.*", 3) 364 } 365 366 func TestSubjectTreeMaxPrefix(t *testing.T) { 367 st := NewSubjectTree[int]() 368 longPre := strings.Repeat("Z", maxPrefixLen+2) 369 for i := 0; i < 2; i++ { 370 subj := b(fmt.Sprintf("%s.%c", longPre, 'A'+i)) 371 old, updated := st.Insert(subj, 22) 372 require_True(t, old == nil) 373 require_False(t, updated) 374 } 375 v, found := st.Find(b(fmt.Sprintf("%s.B", longPre))) 376 require_True(t, found) 377 require_Equal(t, *v, 22) 378 v, found = st.Delete(b(fmt.Sprintf("%s.A", longPre))) 379 require_True(t, found) 380 require_Equal(t, *v, 22) 381 require_Equal(t, st.root.numChildren(), 1) 382 } 383 384 func TestSubjectTreeMatchSubjectParam(t *testing.T) { 385 st := NewSubjectTree[int]() 386 st.Insert(b("foo.bar.A"), 1) 387 st.Insert(b("foo.bar.B"), 2) 388 st.Insert(b("foo.bar.C"), 3) 389 st.Insert(b("foo.baz.A"), 11) 390 st.Insert(b("foo.baz.B"), 22) 391 st.Insert(b("foo.baz.C"), 33) 392 st.Insert(b("foo.bar"), 42) 393 394 checkValMap := map[string]int{ 395 "foo.bar.A": 1, 396 "foo.bar.B": 2, 397 "foo.bar.C": 3, 398 "foo.baz.A": 11, 399 "foo.baz.B": 22, 400 "foo.baz.C": 33, 401 "foo.bar": 42, 402 } 403 // Make sure we get a proper subject parameter and it matches our value properly. 404 st.Match([]byte(">"), func(subject []byte, v *int) { 405 if expected, ok := checkValMap[string(subject)]; !ok { 406 t.Fatalf("Unexpected subject parameter: %q", subject) 407 } else if expected != *v { 408 t.Fatalf("Expected %q to have value of %d, but got %d", subject, expected, *v) 409 } 410 }) 411 } 412 413 func TestSubjectTreeMatchRandomDoublePWC(t *testing.T) { 414 st := NewSubjectTree[int]() 415 for i := 1; i <= 10_000; i++ { 416 subj := fmt.Sprintf("foo.%d.%d", rand.Intn(20)+1, i) 417 st.Insert(b(subj), 42) 418 } 419 match(t, st, "foo.*.*", 10_000) 420 421 // Check with pwc and short interior token. 422 seen, verified := 0, 0 423 st.Match(b("*.2.*"), func(_ []byte, _ *int) { 424 seen++ 425 }) 426 // Now check via walk to make sure we are right. 427 st.Iter(func(subject []byte, v *int) bool { 428 tokens := strings.Split(string(subject), ".") 429 require_Equal(t, len(tokens), 3) 430 if tokens[1] == "2" { 431 verified++ 432 } 433 return true 434 }) 435 require_Equal(t, seen, verified) 436 437 seen, verified = 0, 0 438 st.Match(b("*.*.222"), func(_ []byte, _ *int) { 439 seen++ 440 }) 441 st.Iter(func(subject []byte, v *int) bool { 442 tokens := strings.Split(string(subject), ".") 443 require_Equal(t, len(tokens), 3) 444 if tokens[2] == "222" { 445 verified++ 446 } 447 return true 448 }) 449 require_Equal(t, seen, verified) 450 } 451 452 func TestSubjectTreeIter(t *testing.T) { 453 st := NewSubjectTree[int]() 454 st.Insert(b("foo.bar.A"), 1) 455 st.Insert(b("foo.bar.B"), 2) 456 st.Insert(b("foo.bar.C"), 3) 457 st.Insert(b("foo.baz.A"), 11) 458 st.Insert(b("foo.baz.B"), 22) 459 st.Insert(b("foo.baz.C"), 33) 460 st.Insert(b("foo.bar"), 42) 461 462 checkValMap := map[string]int{ 463 "foo.bar.A": 1, 464 "foo.bar.B": 2, 465 "foo.bar.C": 3, 466 "foo.baz.A": 11, 467 "foo.baz.B": 22, 468 "foo.baz.C": 33, 469 "foo.bar": 42, 470 } 471 checkOrder := []string{ 472 "foo.bar", 473 "foo.bar.A", 474 "foo.bar.B", 475 "foo.bar.C", 476 "foo.baz.A", 477 "foo.baz.B", 478 "foo.baz.C", 479 } 480 var received int 481 walk := func(subject []byte, v *int) bool { 482 if expected := checkOrder[received]; expected != string(subject) { 483 t.Fatalf("Expected %q for %d item returned, got %q", expected, received, subject) 484 } 485 received++ 486 require_True(t, v != nil) 487 if expected := checkValMap[string(subject)]; expected != *v { 488 t.Fatalf("Expected %q to have value of %d, but got %d", subject, expected, *v) 489 } 490 return true 491 } 492 // Kick in the iter. 493 st.Iter(walk) 494 require_Equal(t, received, len(checkOrder)) 495 496 // Make sure we can terminate properly. 497 received = 0 498 st.Iter(func(subject []byte, v *int) bool { 499 received++ 500 return received != 4 501 }) 502 require_Equal(t, received, 4) 503 } 504 505 func TestSubjectTreeInsertSamePivotBug(t *testing.T) { 506 testSubjects := [][]byte{ 507 []byte("0d00.2abbb82c1d.6e16.fa7f85470e.3e46"), 508 []byte("534b12.3486c17249.4dde0666"), 509 []byte("6f26aabd.920ee3.d4d3.5ffc69f6"), 510 []byte("8850.ade3b74c31.aa533f77.9f59.a4bd8415.b3ed7b4111"), 511 []byte("5a75047dcb.5548e845b6.76024a34.14d5b3.80c426.51db871c3a"), 512 []byte("825fa8acfc.5331.00caf8bbbd.107c4b.c291.126d1d010e"), 513 } 514 st := NewSubjectTree[int]() 515 for _, subj := range testSubjects { 516 old, updated := st.Insert(subj, 22) 517 require_True(t, old == nil) 518 require_False(t, updated) 519 if _, found := st.Find(subj); !found { 520 t.Fatalf("Could not find subject %q which should be findable", subj) 521 } 522 } 523 } 524 525 func TestSubjectTreeMatchTsepSecondThenPartialPartBug(t *testing.T) { 526 st := NewSubjectTree[int]() 527 st.Insert(b("foo.xxxxx.foo1234.zz"), 22) 528 st.Insert(b("foo.yyy.foo123.zz"), 22) 529 st.Insert(b("foo.yyybar789.zz"), 22) 530 st.Insert(b("foo.yyy.foo12345.zz"), 22) 531 st.Insert(b("foo.yyy.foo12345.yy"), 22) 532 st.Insert(b("foo.yyy.foo123456789.zz"), 22) 533 match(t, st, "foo.*.foo123456789.*", 1) 534 match(t, st, "foo.*.*.zzz.foo.>", 0) 535 } 536 537 func TestSubjectTreeMatchMultipleWildcardBasic(t *testing.T) { 538 st := NewSubjectTree[int]() 539 st.Insert(b("A.B.C.D.0.G.H.I.0"), 22) 540 st.Insert(b("A.B.C.D.1.G.H.I.0"), 22) 541 match(t, st, "A.B.*.D.1.*.*.I.0", 1) 542 } 543 544 func TestSubjectTreeMatchInvalidWildcard(t *testing.T) { 545 st := NewSubjectTree[int]() 546 st.Insert(b("foo.123"), 22) 547 st.Insert(b("one.two.three.four.five"), 22) 548 st.Insert(b("'*.123"), 22) 549 match(t, st, "invalid.>", 0) 550 match(t, st, ">", 3) 551 match(t, st, `'*.*`, 1) 552 match(t, st, `'*.*.*'`, 0) 553 // None of these should match. 554 match(t, st, "`>`", 0) 555 match(t, st, `">"`, 0) 556 match(t, st, `'>'`, 0) 557 match(t, st, `'*.>'`, 0) 558 match(t, st, `'*.>.`, 0) 559 match(t, st, "`invalid.>`", 0) 560 match(t, st, `'*.*'`, 0) 561 } 562 563 func TestSubjectTreeRandomTrackEntries(t *testing.T) { 564 st := NewSubjectTree[int]() 565 smap := make(map[string]struct{}, 1000) 566 567 // Make sure all added items can be found. 568 check := func() { 569 t.Helper() 570 for subj := range smap { 571 if _, found := st.Find(b(subj)); !found { 572 t.Fatalf("Could not find subject %q which should be findable", subj) 573 } 574 } 575 } 576 577 buf := make([]byte, 10) 578 for i := 0; i < 1000; i++ { 579 var sb strings.Builder 580 // 1-6 tokens. 581 numTokens := rand.Intn(6) + 1 582 for i := 0; i < numTokens; i++ { 583 tlen := rand.Intn(4) + 2 584 tok := buf[:tlen] 585 crand.Read(tok) 586 sb.WriteString(hex.EncodeToString(tok)) 587 if i != numTokens-1 { 588 sb.WriteString(".") 589 } 590 } 591 subj := sb.String() 592 // Avoid dupes since will cause check to fail after we delete messages. 593 if _, ok := smap[subj]; ok { 594 continue 595 } 596 smap[subj] = struct{}{} 597 old, updated := st.Insert(b(subj), 22) 598 require_True(t, old == nil) 599 require_False(t, updated) 600 require_Equal(t, st.Size(), len(smap)) 601 check() 602 } 603 } 604 605 func TestSubjectTreeMetaSize(t *testing.T) { 606 var base meta 607 require_Equal(t, unsafe.Sizeof(base), 28) 608 } 609 610 func b(s string) []byte { 611 return []byte(s) 612 } 613 614 func TestSubjectTreeMatchAllPerf(t *testing.T) { 615 if !*runResults { 616 t.Skip() 617 } 618 st := NewSubjectTree[int]() 619 620 for i := 0; i < 1_000_000; i++ { 621 subj := fmt.Sprintf("subj.%d.%d", rand.Intn(100)+1, i) 622 st.Insert(b(subj), 22) 623 } 624 625 for _, f := range [][]byte{ 626 []byte(">"), 627 []byte("subj.>"), 628 []byte("subj.*.*"), 629 []byte("*.*.*"), 630 []byte("subj.1.*"), 631 []byte("subj.1.>"), 632 []byte("subj.*.1"), 633 []byte("*.*.1"), 634 } { 635 start := time.Now() 636 count := 0 637 st.Match(f, func(_ []byte, _ *int) { 638 count++ 639 }) 640 t.Logf("Match %q took %s and matched %d entries", f, time.Since(start), count) 641 } 642 }