Files
releases/v1/debug/variable_test.go
Summy Wu b3f7c2cb30 debug: allow configuring variable value length limit (#8907)
### Why are the changes in this PR needed?

The DAP debugger currently truncates variable values to a hardcoded
limit of 100 characters, with no way for callers to configure it.
Long values cannot be inspected or copied whole from a debugger UI.

### What are the changes in this PR?

- Add a new `SetMaxVariableLength` Debugger option (in both
  `v1/debug` and the top-level `debug` package)
- A value of 0 disables truncation; the default stays 100 characters
  for backward compatibility
- Plumb the limit through `variableManager` and `namedVar`, so it
  applies to top-level and nested (object/array/set) variables alike
- Relax `truncatedString` to return the original string unchanged
  when the limit is <= 0
- Add `TestTruncatedString` and `TestVariableValueLengthLimit`
  covering the default limit, unlimited (0), negative, custom limits,
  and boundary cases

### Notes

- `go build ./...` and `go test ./v1/debug/...` both pass
- Default behavior is unchanged; the new option is opt-in
- This PR was developed with AI assistance (Claude Code)

### Further comments

Refs #8890

---------

Signed-off-by: summy wu <summy.wu81@gmail.com>
2026-07-31 09:36:34 -05:00

162 lines
4.1 KiB
Go

// Copyright 2026 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package debug
import (
"strings"
"testing"
"github.com/open-policy-agent/opa/v1/ast"
)
func TestTruncatedString(t *testing.T) {
tests := []struct {
name string
s string
max int
expected string
}{
{
name: "no truncation when max is 0",
s: strings.Repeat("a", 200),
max: 0,
expected: strings.Repeat("a", 200),
},
{
name: "no truncation when max is negative",
s: strings.Repeat("a", 200),
max: -1,
expected: strings.Repeat("a", 200),
},
{
name: "no truncation when max is 1 (avoids negative slice)",
s: "abcdefghij",
max: 1,
expected: "abcdefghij",
},
{
name: "no truncation when max is 2",
s: "abcdefghij",
max: 2,
expected: "abcdefghij",
},
{
name: "no truncation when max is 3",
s: "abcdefghij",
max: 3,
expected: "abcdefghij",
},
{
name: "truncation at default limit",
s: strings.Repeat("a", 200),
max: 100,
expected: strings.Repeat("a", 98) + "...",
},
{
name: "no truncation when value is under limit",
s: strings.Repeat("a", 50),
max: 100,
expected: strings.Repeat("a", 50),
},
{
name: "no truncation when value equals limit",
s: strings.Repeat("a", 100),
max: 100,
expected: strings.Repeat("a", 100),
},
{
name: "truncation when value exceeds limit by one",
s: strings.Repeat("a", 101),
max: 100,
expected: strings.Repeat("a", 98) + "...",
},
{
name: "small truncation limit",
s: "abcdefghij",
max: 5,
expected: "abc...",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := truncatedString(tc.s, tc.max)
if got != tc.expected {
t.Errorf("truncatedString(%q, %d) = %q, want %q", tc.s, tc.max, got, tc.expected)
}
})
}
}
func TestVariableValueLengthLimit(t *testing.T) {
// A string value longer than the default 100-character limit.
// ast.String.String() renders strings with surrounding quotes, so the
// expected values below include those quote characters.
longValue := strings.Repeat("x", 150)
// A string value under the limit.
shortValue := strings.Repeat("y", 50)
tests := []struct {
name string
maxVariableLength int
expectedLong string
expectedShort string
}{
{
name: "default limit truncates long values",
maxVariableLength: 100,
expectedLong: `"` + strings.Repeat("x", 97) + "...",
expectedShort: `"` + shortValue + `"`,
},
{
name: "zero limit disables truncation",
maxVariableLength: 0,
expectedLong: `"` + longValue + `"`,
expectedShort: `"` + shortValue + `"`,
},
{
name: "negative limit disables truncation",
maxVariableLength: -1,
expectedLong: `"` + longValue + `"`,
expectedShort: `"` + shortValue + `"`,
},
{
name: "custom limit",
maxVariableLength: 120,
expectedLong: `"` + strings.Repeat("x", 117) + "...",
expectedShort: `"` + shortValue + `"`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
vm := newVariableManager(tc.maxVariableLength)
ref := vm.addVars(func() []namedVar {
return []namedVar{
{name: "long", value: ast.StringTerm(longValue).Value},
{name: "short", value: ast.StringTerm(shortValue).Value},
}
})
vars, err := vm.vars(ref)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
got := map[string]string{}
for _, v := range vars {
got[v.Name()] = v.Value()
}
if got["long"] != tc.expectedLong {
t.Errorf("long variable value = %q (len %d), want %q (len %d)",
got["long"], len(got["long"]), tc.expectedLong, len(tc.expectedLong))
}
if got["short"] != tc.expectedShort {
t.Errorf("short variable value = %q, want %q", got["short"], tc.expectedShort)
}
})
}
}