mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-18 13:34:46 +08:00
refactor(executor): optimize translation logic and improve plugin invocation handling
This commit is contained in:
@@ -885,14 +885,14 @@ func translateGeminiInteractionsRequestBody(ctx context.Context, cfg *config.Con
|
||||
}
|
||||
|
||||
// translateGeminiInteractionsRequestPair translates the working payload and the
|
||||
// payload-config baseline. Identical inputs are translated once when no plugin
|
||||
// hooks are installed. The baseline is captured before model and thinking
|
||||
// mutations, and the working buffer is a separate copy so those mutations cannot
|
||||
// change it. Distinct inputs and plugin hooks keep the existing order: working
|
||||
// payload first, then the payload-config source.
|
||||
// payload-config baseline. Identical inputs are translated once, including plugin
|
||||
// hooks. The baseline is captured before model and thinking mutations, and the
|
||||
// working buffer is a separate copy so those mutations cannot change it. Distinct
|
||||
// inputs keep the existing order: working payload first, then the payload-config
|
||||
// source.
|
||||
func translateGeminiInteractionsRequestPair(ctx context.Context, cfg *config.Config, model string, payload []byte, opts cliproxyexecutor.Options, stream, isCompat bool) (original, working []byte) {
|
||||
source := geminiInteractionsPayloadConfigInput(opts, payload)
|
||||
if geminiInteractionsSameByteSlice(payload, source) && !sdktranslator.HasPluginHooks() {
|
||||
if geminiInteractionsSameByteSlice(payload, source) {
|
||||
original = translateGeminiInteractionsRequestBody(ctx, cfg, model, payload, opts, stream, isCompat)
|
||||
return original, bytes.Clone(original)
|
||||
}
|
||||
|
||||
@@ -76,9 +76,6 @@ func TestTranslateGeminiInteractionsRequestPairTranslatesSameSliceOnce(t *testin
|
||||
if sdktranslator.HasRequestTransformer(from, to) {
|
||||
t.Fatalf("request transformer %s -> %s is already registered", from, to)
|
||||
}
|
||||
if sdktranslator.HasPluginHooks() {
|
||||
t.Fatal("plugin hooks are installed and disable translation reuse")
|
||||
}
|
||||
|
||||
const model = "gemini-interactions-count"
|
||||
var calls int
|
||||
@@ -156,7 +153,7 @@ func TestTranslateGeminiInteractionsRequestPairTranslatesDistinctInputs(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateGeminiInteractionsRequestPairPreservesHookOrder(t *testing.T) {
|
||||
func TestTranslateGeminiInteractionsRequestPairInvokesPluginOncePerInput(t *testing.T) {
|
||||
hooks := &interactionsTranslateHooks{}
|
||||
sdktranslator.SetPluginHooks(hooks)
|
||||
t.Cleanup(func() { sdktranslator.SetPluginHooks(nil) })
|
||||
@@ -166,22 +163,27 @@ func TestTranslateGeminiInteractionsRequestPairPreservesHookOrder(t *testing.T)
|
||||
const model = "gemini-3.1-flash-lite"
|
||||
payload := []byte(`{"model":"gemini-3.1-flash-lite","messages":[{"role":"user","content":"same"}]}`)
|
||||
opts := cliproxyexecutor.Options{SourceFormat: sdktranslator.FormatOpenAI, OriginalRequest: payload}
|
||||
base, work := translateGeminiInteractionsRequestPair(ctx, cfg, model, payload, opts, false, true)
|
||||
if hooks.calls != 2 {
|
||||
t.Fatalf("plugin hook calls = %d, want 2", hooks.calls)
|
||||
for _, stream := range []bool{false, true} {
|
||||
for _, compat := range []bool{false, true} {
|
||||
hooks.calls = 0
|
||||
base, work := translateGeminiInteractionsRequestPair(ctx, cfg, model, payload, opts, stream, compat)
|
||||
if hooks.calls != 1 {
|
||||
t.Fatalf("stream=%v compat=%v: plugin calls = %d, want 1", stream, compat, hooks.calls)
|
||||
}
|
||||
if got := gjson.GetBytes(work, "plugin_call").Int(); got != 1 {
|
||||
t.Fatalf("working plugin_call = %d, want 1", got)
|
||||
}
|
||||
if !bytes.Equal(base, work) {
|
||||
t.Fatal("same input produced different plugin results")
|
||||
}
|
||||
assertIndependentGeminiInteractionsBuffers(t, payload, base, work)
|
||||
}
|
||||
}
|
||||
if got := gjson.GetBytes(work, "plugin_call").Int(); got != 1 {
|
||||
t.Fatalf("working plugin_call = %d, want 1", got)
|
||||
}
|
||||
if got := gjson.GetBytes(base, "plugin_call").Int(); got != 2 {
|
||||
t.Fatalf("baseline plugin_call = %d, want 2", got)
|
||||
}
|
||||
assertIndependentGeminiInteractionsBuffers(t, payload, base, work)
|
||||
|
||||
detached := bytes.Clone(payload)
|
||||
opts.OriginalRequest = detached
|
||||
before := hooks.calls
|
||||
base, work = translateGeminiInteractionsRequestPair(ctx, cfg, model, payload, opts, true, false)
|
||||
base, work := translateGeminiInteractionsRequestPair(ctx, cfg, model, payload, opts, true, false)
|
||||
if hooks.calls != before+2 {
|
||||
t.Fatalf("distinct plugin hook calls = %d, want %d", hooks.calls-before, 2)
|
||||
}
|
||||
|
||||
@@ -59,12 +59,10 @@ func TranslateRequestEnvelopeWithCodexMultiAgentV2(ctx context.Context, headers
|
||||
|
||||
// TranslateRequestPairWithCodexMultiAgentV2 translates the untouched baseline
|
||||
// payload and the working payload that later stages mutate in place. Executors
|
||||
// normally assign the original payload to the request before translating, so both
|
||||
// translations would rescan the same bytes and produce the same result. Built-in
|
||||
// request translation is deterministic, so that case is translated once and
|
||||
// duplicated when no plugin hooks are installed. Hooks retain two invocations
|
||||
// because they may have request-scoped output or side effects. This removes a
|
||||
// full extra pass over payloads that can reach tens of megabytes.
|
||||
// normally assign the original payload to the request before translating, so the
|
||||
// same input is translated once, including plugin hooks, then copied into a
|
||||
// separate working buffer. This avoids duplicate plugin side effects and a full
|
||||
// extra pass over payloads that can reach tens of megabytes.
|
||||
func TranslateRequestPairWithCodexMultiAgentV2(ctx context.Context, headers http.Header, cfg *config.Config, from, to sdktranslator.Format, model string, originalPayload, requestPayload []byte, stream bool) (original, working []byte) {
|
||||
req := sdktranslator.RequestEnvelope{Format: from, Model: model, Stream: stream}
|
||||
return TranslateRequestEnvelopePairWithCodexMultiAgentV2(ctx, headers, cfg, from, to, req, originalPayload, requestPayload)
|
||||
@@ -76,7 +74,7 @@ func TranslateRequestEnvelopePairWithCodexMultiAgentV2(ctx context.Context, head
|
||||
originalReq := req
|
||||
originalReq.Body = originalPayload
|
||||
original = TranslateRequestEnvelopeWithCodexMultiAgentV2(ctx, headers, cfg, from, to, originalReq).Body
|
||||
if sameByteSlice(originalPayload, requestPayload) && !sdktranslator.HasPluginHooks() {
|
||||
if sameByteSlice(originalPayload, requestPayload) {
|
||||
// The caller mutates the working copy, so it must not share the baseline array.
|
||||
return original, append([]byte(nil), original...)
|
||||
}
|
||||
@@ -99,7 +97,7 @@ func sameByteSlice(a, b []byte) bool {
|
||||
}
|
||||
|
||||
// TranslateRequestPairWithAPIKeyModelCompatibility avoids translating identical
|
||||
// inputs twice while retaining separate buffers and stateful plugin invocations.
|
||||
// inputs twice while retaining separate buffers and invoking plugins once per input.
|
||||
func TranslateRequestPairWithAPIKeyModelCompatibility(ctx context.Context, headers http.Header, cfg *config.Config, from, to sdktranslator.Format, model string, originalPayload, requestPayload []byte, stream, isCompat bool) (original, working []byte) {
|
||||
original, working, _ = TranslateRequestPairWithAPIKeyModelCompatibilityAndUpdateIntent(ctx, headers, cfg, from, to, model, originalPayload, requestPayload, stream, isCompat)
|
||||
return original, working
|
||||
@@ -108,9 +106,9 @@ func TranslateRequestPairWithAPIKeyModelCompatibility(ctx context.Context, heade
|
||||
// TranslateRequestPairWithAPIKeyModelCompatibilityAndUpdateIntent returns the
|
||||
// normalizer decision for the working payload, not for the baseline payload.
|
||||
func TranslateRequestPairWithAPIKeyModelCompatibilityAndUpdateIntent(ctx context.Context, headers http.Header, cfg *config.Config, from, to sdktranslator.Format, model string, originalPayload, requestPayload []byte, stream, isCompat bool) (original, working []byte, updatesChanged bool) {
|
||||
original, _ = TranslateRequestWithAPIKeyModelCompatibilityAndUpdateIntent(ctx, headers, cfg, from, to, model, originalPayload, stream, isCompat)
|
||||
if sameByteSlice(originalPayload, requestPayload) && !sdktranslator.HasPluginHooks() {
|
||||
return original, append([]byte(nil), original...), false
|
||||
original, updatesChanged = TranslateRequestWithAPIKeyModelCompatibilityAndUpdateIntent(ctx, headers, cfg, from, to, model, originalPayload, stream, isCompat)
|
||||
if sameByteSlice(originalPayload, requestPayload) {
|
||||
return original, append([]byte(nil), original...), updatesChanged
|
||||
}
|
||||
working, updatesChanged = TranslateRequestWithAPIKeyModelCompatibilityAndUpdateIntent(ctx, headers, cfg, from, to, model, requestPayload, stream, isCompat)
|
||||
return original, working, updatesChanged
|
||||
|
||||
@@ -132,32 +132,45 @@ func TestTranslateRequestPairTranslatesDistinctPayloads(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateRequestPairPreservesPluginHookInvocations(t *testing.T) {
|
||||
func TestTranslateRequestPairInvokesPluginOncePerInput(t *testing.T) {
|
||||
hooks := &pairRequestPluginHooks{}
|
||||
sdktranslator.SetPluginHooks(hooks)
|
||||
t.Cleanup(func() { sdktranslator.SetPluginHooks(nil) })
|
||||
|
||||
payload := geminiToolHistoryPayload(1)
|
||||
base, work := TranslateRequestPairWithCodexMultiAgentV2(
|
||||
context.Background(),
|
||||
http.Header{},
|
||||
&config.Config{},
|
||||
sdktranslator.FormatGemini,
|
||||
sdktranslator.FromString("antigravity"),
|
||||
"gemini-3.6-flash-high",
|
||||
payload,
|
||||
payload,
|
||||
true,
|
||||
)
|
||||
|
||||
if hooks.calls != 2 {
|
||||
t.Fatalf("plugin hook calls = %d, want 2", hooks.calls)
|
||||
}
|
||||
if got := gjson.GetBytes(base, "plugin_call").Int(); got != 1 {
|
||||
t.Fatalf("baseline plugin_call = %d, want 1", got)
|
||||
}
|
||||
if got := gjson.GetBytes(work, "plugin_call").Int(); got != 2 {
|
||||
t.Fatalf("working plugin_call = %d, want 2", got)
|
||||
for _, stream := range []bool{false, true} {
|
||||
for _, distinct := range []bool{false, true} {
|
||||
hooks.calls = 0
|
||||
request := payload
|
||||
wantCalls := int64(1)
|
||||
if distinct {
|
||||
request = geminiToolHistoryPayload(2)
|
||||
wantCalls = 2
|
||||
}
|
||||
base, work := TranslateRequestPairWithCodexMultiAgentV2(
|
||||
context.Background(), http.Header{}, &config.Config{},
|
||||
sdktranslator.FormatGemini, sdktranslator.FormatAntigravity,
|
||||
"gemini-3.6-flash-high", payload, request, stream,
|
||||
)
|
||||
if hooks.calls != wantCalls {
|
||||
t.Fatalf("stream=%v distinct=%v: plugin calls = %d, want %d", stream, distinct, hooks.calls, wantCalls)
|
||||
}
|
||||
if got := gjson.GetBytes(base, "plugin_call").Int(); got != 1 {
|
||||
t.Fatalf("baseline plugin_call = %d, want 1", got)
|
||||
}
|
||||
if got := gjson.GetBytes(work, "plugin_call").Int(); got != wantCalls {
|
||||
t.Fatalf("working plugin_call = %d, want %d", got, wantCalls)
|
||||
}
|
||||
if !distinct && !bytes.Equal(base, work) {
|
||||
t.Fatal("same input produced different plugin results")
|
||||
}
|
||||
baselineBefore := bytes.Clone(base)
|
||||
inputBefore := bytes.Clone(request)
|
||||
work[0] = 'X'
|
||||
if !bytes.Equal(base, baselineBefore) || !bytes.Equal(request, inputBefore) {
|
||||
t.Fatal("working buffer aliases the baseline or input")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package helps_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/registry"
|
||||
helps "github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor/helps"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v8/internal/thinking/provider/claude"
|
||||
@@ -18,9 +20,11 @@ import (
|
||||
type editUpdatePluginHooks struct {
|
||||
summaryRemovingPluginHooks
|
||||
effort string
|
||||
calls int
|
||||
}
|
||||
|
||||
func (h *editUpdatePluginHooks) NormalizeRequest(_ context.Context, _, _ sdktranslator.Format, _ string, body []byte, _ bool) []byte {
|
||||
h.calls++
|
||||
if h.effort == "" {
|
||||
out, _ := sjson.DeleteBytes(body, "input.0")
|
||||
return out
|
||||
@@ -57,6 +61,41 @@ func TestPluginConfigurationUpdateNormalizationIsAuthoritative(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestPairPreservesPluginConfigurationUpdateIntent(t *testing.T) {
|
||||
for _, stream := range []bool{false, true} {
|
||||
for _, compat := range []bool{false, true} {
|
||||
for _, distinct := range []bool{false, true} {
|
||||
hooks := &editUpdatePluginHooks{}
|
||||
sdktranslator.SetPluginHooks(hooks)
|
||||
t.Cleanup(func() { sdktranslator.SetPluginHooks(nil) })
|
||||
original := []byte(`{"reasoning":{"effort":"medium"},"input":[{"type":"configuration_update","reasoning":{"effort":"low"}},{"role":"user","content":"hi"}]}`)
|
||||
request := original
|
||||
wantCalls := 1
|
||||
if distinct {
|
||||
request = []byte(`{"input":[{"role":"user","content":"different"}]}`)
|
||||
wantCalls = 2
|
||||
}
|
||||
inputBefore := bytes.Clone(request)
|
||||
base, work, updatesChanged := helps.TranslateRequestPairWithAPIKeyModelCompatibilityAndUpdateIntent(t.Context(), nil, &config.Config{}, sdktranslator.FormatOpenAIResponse, sdktranslator.FormatCodex, "opaque-route", original, request, stream, compat)
|
||||
if hooks.calls != wantCalls {
|
||||
t.Fatalf("stream=%v compat=%v distinct=%v: plugin calls = %d, want %d", stream, compat, distinct, hooks.calls, wantCalls)
|
||||
}
|
||||
if updatesChanged != !distinct {
|
||||
t.Fatalf("stream=%v compat=%v distinct=%v: working update intent = %v, want %v", stream, compat, distinct, updatesChanged, !distinct)
|
||||
}
|
||||
if !distinct && !bytes.Equal(base, work) {
|
||||
t.Fatal("same input produced different plugin results")
|
||||
}
|
||||
baselineBefore := bytes.Clone(base)
|
||||
work[0] = 'X'
|
||||
if !bytes.Equal(base, baselineBefore) || !bytes.Equal(request, inputBefore) {
|
||||
t.Fatal("working buffer aliases the baseline or input")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeCrossProtocolTranslationWithoutPluginRetainsSourceUpdate(t *testing.T) {
|
||||
const source = `{"model":"private-chat","reasoning":{"effort":"medium"},"input":[{"type":"configuration_update","reasoning":{"effort":"high"}},{"role":"user","content":"hi"}]}`
|
||||
translated := sdktranslator.TranslateRequestEnvelope(t.Context(), sdktranslator.FormatOpenAIResponse, sdktranslator.FormatOpenAI, sdktranslator.RequestEnvelope{Model: "private-chat", Body: []byte(source)})
|
||||
|
||||
@@ -45,9 +45,6 @@ func TestTranslateRequestPairWithAPIKeyModelCompatibilityCountsTranslations(t *t
|
||||
if sdktranslator.HasRequestTransformer(from, to) {
|
||||
t.Fatalf("request transformer %s -> %s is already registered", from, to)
|
||||
}
|
||||
if sdktranslator.HasPluginHooks() {
|
||||
t.Fatal("plugin hooks are installed and disable translation reuse")
|
||||
}
|
||||
|
||||
const model = "compat-count-model"
|
||||
var calls int
|
||||
@@ -103,13 +100,35 @@ func TestTranslateRequestPairWithAPIKeyModelCompatibilityCountsTranslations(t *t
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompatibilityRequestPairPreservesHooks(t *testing.T) {
|
||||
func TestCompatibilityRequestPairInvokesPluginOncePerInput(t *testing.T) {
|
||||
hooks := &pairRequestPluginHooks{}
|
||||
sdktranslator.SetPluginHooks(hooks)
|
||||
t.Cleanup(func() { sdktranslator.SetPluginHooks(nil) })
|
||||
request := []byte(`{"model":"test","input":"hello"}`)
|
||||
base, work := TranslateRequestPairWithAPIKeyModelCompatibility(context.Background(), nil, &config.Config{}, sdktranslator.FormatOpenAIResponse, sdktranslator.FormatOpenAI, "test", request, request, true, true)
|
||||
if hooks.calls != 2 || bytes.Equal(base, work) {
|
||||
t.Fatalf("stateful plugin calls must remain independent, calls=%d", hooks.calls)
|
||||
payload := []byte(`{"model":"test","input":"hello"}`)
|
||||
for _, stream := range []bool{false, true} {
|
||||
for _, compat := range []bool{false, true} {
|
||||
for _, distinct := range []bool{false, true} {
|
||||
hooks.calls = 0
|
||||
request := payload
|
||||
wantCalls := int64(1)
|
||||
if distinct {
|
||||
request = []byte(`{"model":"test","input":"changed"}`)
|
||||
wantCalls = 2
|
||||
}
|
||||
base, work := TranslateRequestPairWithAPIKeyModelCompatibility(context.Background(), nil, &config.Config{}, sdktranslator.FormatOpenAIResponse, sdktranslator.FormatOpenAI, "test", payload, request, stream, compat)
|
||||
if hooks.calls != wantCalls {
|
||||
t.Fatalf("stream=%v compat=%v distinct=%v: plugin calls = %d, want %d", stream, compat, distinct, hooks.calls, wantCalls)
|
||||
}
|
||||
if bytes.Equal(base, work) == distinct {
|
||||
t.Fatal("plugin results do not match input identity")
|
||||
}
|
||||
baselineBefore := bytes.Clone(base)
|
||||
inputBefore := bytes.Clone(request)
|
||||
work[0] = 'X'
|
||||
if !bytes.Equal(base, baselineBefore) || !bytes.Equal(request, inputBefore) {
|
||||
t.Fatal("working buffer aliases the baseline or input")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package helps_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/pluginhost"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor/helps"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v8/sdk/translator"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestRequestPairWithEmptyPluginHostSanitizesSignaturesOnce(t *testing.T) {
|
||||
logger := log.StandardLogger()
|
||||
oldOutput, oldLevel := logger.Out, logger.GetLevel()
|
||||
var logs bytes.Buffer
|
||||
logger.SetOutput(&logs)
|
||||
logger.SetLevel(log.DebugLevel)
|
||||
t.Cleanup(func() {
|
||||
logger.SetOutput(oldOutput)
|
||||
logger.SetLevel(oldLevel)
|
||||
sdktranslator.SetPluginHooks(nil)
|
||||
})
|
||||
|
||||
var items []string
|
||||
for i := range 10 {
|
||||
items = append(items, fmt.Sprintf(`{"type":"function_call","call_id":"call_%d","name":"read_file","arguments":"{}"}`, i))
|
||||
}
|
||||
for i := range 10 {
|
||||
items = append(items, fmt.Sprintf(`{"type":"function_call_output","call_id":"call_%d","output":"ok"}`, i))
|
||||
}
|
||||
items = append(items, `{"role":"user","content":"continue"}`)
|
||||
payload := []byte(`{"model":"gemini-3.6-flash-high","input":[` + strings.Join(items, ",") + `]}`)
|
||||
inputBefore := bytes.Clone(payload)
|
||||
|
||||
for _, host := range []sdktranslator.PluginHooks{nil, pluginhost.New()} {
|
||||
sdktranslator.SetPluginHooks(host)
|
||||
for _, stream := range []bool{false, true} {
|
||||
for _, route := range []string{"envelope", "api-key", "api-key-compat"} {
|
||||
logs.Reset()
|
||||
var base, work []byte
|
||||
if route == "envelope" {
|
||||
req := sdktranslator.RequestEnvelope{Format: sdktranslator.FormatOpenAIResponse, Model: "gemini-3.6-flash-high", Stream: stream}
|
||||
base, work = helps.TranslateRequestEnvelopePairWithCodexMultiAgentV2(t.Context(), nil, &config.Config{}, sdktranslator.FormatOpenAIResponse, sdktranslator.FormatAntigravity, req, payload, payload)
|
||||
} else {
|
||||
base, work = helps.TranslateRequestPairWithAPIKeyModelCompatibility(t.Context(), nil, &config.Config{}, sdktranslator.FormatOpenAIResponse, sdktranslator.FormatAntigravity, "gemini-3.6-flash-high", payload, payload, stream, route == "api-key-compat")
|
||||
}
|
||||
const message = "gemini request: sanitized 9 thoughtSignatures"
|
||||
if count := strings.Count(logs.String(), message); count != 1 {
|
||||
t.Fatalf("host=%T stream=%v route=%s: sanitation logs = %d, want 1; logs=%s", host, stream, route, count, logs.String())
|
||||
}
|
||||
if !bytes.Equal(base, work) {
|
||||
t.Fatal("same input produced different translations")
|
||||
}
|
||||
parts := gjson.GetBytes(work, "request.contents.0.parts").Array()
|
||||
if len(parts) != 10 || parts[0].Get("thoughtSignature").String() != "skip_thought_signature_validator" {
|
||||
t.Fatalf("unexpected parallel tool call layout: %s", work)
|
||||
}
|
||||
for _, part := range parts[1:] {
|
||||
if part.Get("thoughtSignature").Exists() {
|
||||
t.Fatal("sibling tool call still has a signature")
|
||||
}
|
||||
}
|
||||
baselineBefore := bytes.Clone(base)
|
||||
work[0] = 'X'
|
||||
if !bytes.Equal(base, baselineBefore) || !bytes.Equal(payload, inputBefore) {
|
||||
t.Fatal("working buffer aliases the baseline or input")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user