Skip to content

Commit c87ce51

Browse files
gh123manclaude
andcommitted
test(logs): pin the pre-collapse IIS shape as a fixture
Drops skipHybridCollapse. The flag only existed so a test could drive emitRuns without collapsing, which put test-only state on the production Tokenizer to work around collapsing having moved into emitToken. The "without collapse" half of the IIS test is really about the scorer: given a token sequence where the client IP is still seven run tokens, Kadane averages 0.5 and the line stays aggregate. That sequence is a fixture, not something the tokenizer needs to be able to reproduce. Expressing it as one, split around the client IP, also lets the same fixture state the post-collapse shape, and a require.Equal against the real Tokenize output pins it so the baseline cannot drift. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8dcf6bb commit c87ce51

2 files changed

Lines changed: 35 additions & 18 deletions

File tree

pkg/logs/internal/decoder/preprocessor/timestamp_detector_test.go

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -159,17 +159,34 @@ func defaultTimestampDetectorSettings(t *testing.T) (threshold float64, labelerM
159159
return threshold, labelerMaxBytes
160160
}
161161

162-
func tokenizeWithoutHybridCollapse(tok *Tokenizer, input []byte) []Token {
163-
maxBytes := len(input)
164-
if tok.maxEvalBytes > 0 && tok.maxEvalBytes < maxBytes {
165-
maxBytes = tok.maxEvalBytes
162+
// The token sequence for the first tokenizer_max_input_bytes of
163+
// iisW3CFailingShape, split around the client IP so the same fixture expresses
164+
// both the pre- and post-collapse shapes:
165+
//
166+
// DDDD-DD-DD DD:DD:DD CDCCCD <ip> CCC /CCCCC/CCCCCCC/CDD
167+
//
168+
// Before the fix the IP was seven separate run tokens, which is what let Kadane
169+
// extend the timestamp match across it; now it is one IPv4 token.
170+
var (
171+
iisW3CTokenPrefix = []Token{
172+
D4, Dash, D2, Dash, D2, Space, // 2026-08-11
173+
D2, Colon, D2, Colon, D2, Space, // 10:34:49
174+
C1, D1, C3, D1, Space, // W3SVC1
175+
}
176+
iisW3CClientIPRuns = []Token{D2, Period, D1, Period, D2, Period, D2} // 10.1.48.10
177+
iisW3CTokenSuffix = []Token{
178+
Space, C3, Space, // GET
179+
Fslash, C5, Fslash, C7, Fslash, C1, D2, // /ZenIT/Service/v13
166180
}
167-
tok.skipHybridCollapse = true
168-
tok.emitRuns(input[:maxBytes])
169-
tok.skipHybridCollapse = false
170-
out := make([]Token, len(tok.tsBuf))
171-
copy(out, tok.tsBuf)
172-
return out
181+
)
182+
183+
// iisW3CTokens builds the fixture with the client IP rendered as the given
184+
// tokens: the seven run tokens for the pre-collapse shape, or a single IPv4.
185+
func iisW3CTokens(clientIP ...Token) []Token {
186+
out := make([]Token, 0, len(iisW3CTokenPrefix)+len(clientIP)+len(iisW3CTokenSuffix))
187+
out = append(out, iisW3CTokenPrefix...)
188+
out = append(out, clientIP...)
189+
return append(out, iisW3CTokenSuffix...)
173190
}
174191

175192
// TestIISW3CDottedQuadDoesNotDiluteTimestampScore is the unit-level proof
@@ -186,7 +203,7 @@ func TestIISW3CDottedQuadDoesNotDiluteTimestampScore(t *testing.T) {
186203
detector := NewTimestampDetector(threshold)
187204
raw := []byte(iisW3CFailingShape)
188205

189-
without := tokenizeWithoutHybridCollapse(tok, raw)
206+
without := iisW3CTokens(iisW3CClientIPRuns...)
190207
matchWithout := staticTokenGraph.MatchProbability(without)
191208
assert.Equal(t, 0.5, matchWithout.probability, "pre-fix Kadane average over timestamp+IP must be 0.5")
192209

@@ -195,6 +212,11 @@ func TestIISW3CDottedQuadDoesNotDiluteTimestampScore(t *testing.T) {
195212
assert.Equal(t, aggregate, ctxWithout.label, "without IPv4 collapse the IIS line must stay aggregate at threshold 0.5")
196213

197214
with, _ := tok.Tokenize(raw)
215+
// Pins the fixture above to what the tokenizer actually emits, so the
216+
// pre-collapse baseline cannot drift away from the real token sequence.
217+
require.Equal(t, iisW3CTokens(IPv4), with,
218+
"fixture must match the tokenizer output apart from the collapsed client IP")
219+
198220
matchWith := staticTokenGraph.MatchProbability(with)
199221
assert.Equal(t, 1.0, matchWith.probability, "after collapse Kadane must stay on the timestamp-only run")
200222

pkg/logs/internal/decoder/preprocessor/tokenizer.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,6 @@ type Tokenizer struct {
7676
maxEvalBytes int
7777
tsBuf []Token // Reusable token buffer
7878
idxBuf []int // Reusable index buffer
79-
// skipHybridCollapse disables hybrid-token collapsing so tests can
80-
// reconstruct the pre-IPv4-token tokenizer. Zero value collapses, so a
81-
// zero-value Tokenizer still behaves like a production one.
82-
skipHybridCollapse bool
8379
}
8480

8581
// NewTokenizer returns a new Tokenizer detection heuristic.
@@ -160,7 +156,7 @@ func (t *Tokenizer) emitToken(input []byte, token Token, start, end int) {
160156
// A dotted quad can only ever be closed by its final octet, so this is
161157
// the one emission that can complete the pattern. Checking here keeps
162158
// the cost off every other token and avoids a second pass entirely.
163-
if token == D1 && runLen <= maxIPv4OctetDigits && !t.skipHybridCollapse {
159+
if token == D1 && runLen <= maxIPv4OctetDigits {
164160
t.collapseIPv4Tail()
165161
}
166162
return
@@ -180,8 +176,7 @@ func (t *Tokenizer) tokenizeIntoBuffers(input []byte) ([]Token, []int) {
180176
}
181177

182178
// emitRuns run-length-encodes input into tsBuf/idxBuf, collapsing hybrid tokens
183-
// as they complete unless skipHybridCollapse is set. Tests set that flag to
184-
// reconstruct the pre-IPv4-token tokenizer for the IIS false-aggregate case.
179+
// via emitToken as each one completes.
185180
func (t *Tokenizer) emitRuns(input []byte) {
186181
inputLen := len(input)
187182
if inputLen == 0 {

0 commit comments

Comments
 (0)