Skip to content

Commit 86f8415

Browse files
La002g0Lakshana
andauthored
mcp: use synctest for timing-dependent tests (#756)
Converted timing-based tests to use Go 1.25's synctest for instant, deterministic execution with simulated time. Moved converted tests to *_go125_test.go files. Tests using real I/O (exec.Command, httptest) remain timing-based as they require actual system operations. Fixes #680 --------- Co-authored-by: i586147 <lakshana.goswami@sap.com>
1 parent 41b54dc commit 86f8415

File tree

10 files changed

+895
-3
lines changed

10 files changed

+895
-3
lines changed

examples/server/auth-middleware/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
)
99

1010
require (
11-
github.com/google/jsonschema-go v0.3.0 // indirect
11+
github.com/google/jsonschema-go v0.4.2 // indirect
1212
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
1313
golang.org/x/oauth2 v0.30.0 // indirect
1414
)

examples/server/auth-middleware/go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
44
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
55
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
66
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
7+
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
78
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
89
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
910
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=

examples/server/rate-limiting/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
)
99

1010
require (
11-
github.com/google/jsonschema-go v0.3.0 // indirect
11+
github.com/google/jsonschema-go v0.4.2 // indirect
1212
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
1313
)
1414

examples/server/rate-limiting/go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
22
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
33
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
44
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
5+
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
56
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
67
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
78
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=

mcp/cmd_go125_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
2+
// Use of this source code is governed by an MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.25
6+
7+
package mcp_test
8+
9+
import (
10+
"context"
11+
"errors"
12+
"testing"
13+
"testing/synctest"
14+
15+
"github.com/modelcontextprotocol/go-sdk/mcp"
16+
)
17+
18+
func TestServerRunContextCancel_Synctest(t *testing.T) {
19+
synctest.Test(t, func(t *testing.T) {
20+
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v0.0.1"}, nil)
21+
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
22+
23+
ctx, cancel := context.WithCancel(context.Background())
24+
defer cancel()
25+
26+
serverTransport, clientTransport := mcp.NewInMemoryTransports()
27+
28+
// run the server and capture the exit error
29+
onServerExit := make(chan error)
30+
go func() {
31+
onServerExit <- server.Run(ctx, serverTransport)
32+
}()
33+
34+
// send a ping to the server to ensure it's running
35+
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
36+
session, err := client.Connect(ctx, clientTransport, nil)
37+
if err != nil {
38+
t.Fatal(err)
39+
}
40+
t.Cleanup(func() { session.Close() })
41+
42+
if err := session.Ping(context.Background(), nil); err != nil {
43+
t.Fatal(err)
44+
}
45+
46+
// cancel the context to stop the server
47+
cancel()
48+
49+
// wait for the server to exit
50+
51+
err = <-onServerExit
52+
if !errors.Is(err, context.Canceled) {
53+
t.Fatalf("server did not exit after context cancellation, got error: %v", err)
54+
}
55+
})
56+
}

mcp/cmd_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func runCancelContextServer() {
7676
}
7777
}
7878

79+
// TODO: remove this test when Go 1.24 support is dropped (use go1.25 synctest version).
7980
func TestServerRunContextCancel(t *testing.T) {
8081
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v0.0.1"}, nil)
8182
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
@@ -107,7 +108,7 @@ func TestServerRunContextCancel(t *testing.T) {
107108
cancel()
108109

109110
// wait for the server to exit
110-
// TODO: use synctest when availble
111+
// TODO: use synctest when available
111112
select {
112113
case <-time.After(5 * time.Second):
113114
t.Fatal("server did not exit after context cancellation")

mcp/elicitation_go125_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
2+
// Use of this source code is governed by an MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.25
6+
7+
package mcp
8+
9+
import (
10+
"context"
11+
"testing"
12+
"testing/synctest"
13+
)
14+
15+
func TestElicitationCompleteNotification_Synctest(t *testing.T) {
16+
synctest.Test(t, func(t *testing.T) {
17+
ctx := context.Background()
18+
19+
var elicitationCompleteCh = make(chan *ElicitationCompleteParams, 1)
20+
21+
c := NewClient(testImpl, &ClientOptions{
22+
Capabilities: &ClientCapabilities{
23+
Roots: RootCapabilities{ListChanged: true},
24+
RootsV2: &RootCapabilities{ListChanged: true},
25+
Elicitation: &ElicitationCapabilities{
26+
URL: &URLElicitationCapabilities{},
27+
},
28+
},
29+
ElicitationHandler: func(context.Context, *ElicitRequest) (*ElicitResult, error) {
30+
return &ElicitResult{Action: "accept"}, nil
31+
},
32+
ElicitationCompleteHandler: func(_ context.Context, req *ElicitationCompleteNotificationRequest) {
33+
elicitationCompleteCh <- req.Params
34+
},
35+
})
36+
37+
_, ss, cleanup := basicClientServerConnection(t, c, nil, nil)
38+
defer cleanup()
39+
40+
// 1. Server initiates a URL elicitation
41+
elicitID := "testElicitationID-123"
42+
resp, err := ss.Elicit(ctx, &ElicitParams{
43+
Mode: "url",
44+
Message: "Please complete this form: ",
45+
URL: "https://example.com/form?id=" + elicitID,
46+
ElicitationID: elicitID,
47+
})
48+
if err != nil {
49+
t.Fatalf("Elicit failed: %v", err)
50+
}
51+
if resp.Action != "accept" {
52+
t.Fatalf("Elicit action is %q, want %q", resp.Action, "accept")
53+
}
54+
55+
// 2. Server sends elicitation complete notification (simulating out-of-band completion)
56+
err = handleNotify(ctx, notificationElicitationComplete, newServerRequest(ss, &ElicitationCompleteParams{
57+
ElicitationID: elicitID,
58+
}))
59+
if err != nil {
60+
t.Fatalf("failed to send elicitation complete notification: %v", err)
61+
}
62+
63+
// 3. Client should receive the notification
64+
gotParams := <-elicitationCompleteCh
65+
if gotParams.ElicitationID != elicitID {
66+
t.Errorf("elicitationComplete notification ID mismatch: got %q, want %q", gotParams.ElicitationID, elicitID)
67+
}
68+
})
69+
}

mcp/elicitation_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func TestElicitationURLMode(t *testing.T) {
143143
}
144144
}
145145

146+
// TODO: remove this test when Go 1.24 support is dropped (use go1.25 synctest version).
146147
func TestElicitationCompleteNotification(t *testing.T) {
147148
ctx := context.Background()
148149

0 commit comments

Comments
 (0)