mirror of
https://github.com/go-i2p/go-i2ptunnel.git
synced 2025-12-20 15:15:52 -05:00
Add missing methods to implement complete interfaces
This commit is contained in:
@@ -24,6 +24,7 @@ Key features:
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -69,7 +70,11 @@ func (h *HTTPClient) recordError(err error) {
|
|||||||
|
|
||||||
// Get the tunnel's I2P address
|
// Get the tunnel's I2P address
|
||||||
func (h *HTTPClient) Address() string {
|
func (h *HTTPClient) Address() string {
|
||||||
return h.Garlic.B32()
|
// For HTTP client proxy, return the service address if available
|
||||||
|
if h.Garlic != nil && h.Garlic.ServiceKeys != nil {
|
||||||
|
return h.Garlic.ServiceKeys.Addr().Base32()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the tunnel's error message
|
// Get the tunnel's error message
|
||||||
@@ -151,3 +156,44 @@ func (h *HTTPClient) Target() string {
|
|||||||
func (h *HTTPClient) Type() string {
|
func (h *HTTPClient) Type() string {
|
||||||
return h.TunnelConfig.Type
|
return h.TunnelConfig.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's ID
|
||||||
|
func (h *HTTPClient) ID() string {
|
||||||
|
return i2ptunnel.Clean(h.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's options
|
||||||
|
func (h *HTTPClient) Options() map[string]string {
|
||||||
|
// Return basic configuration options as a map
|
||||||
|
options := make(map[string]string)
|
||||||
|
options["name"] = h.TunnelConfig.Name
|
||||||
|
options["type"] = h.TunnelConfig.Type
|
||||||
|
options["interface"] = h.TunnelConfig.Interface
|
||||||
|
options["port"] = strconv.Itoa(h.TunnelConfig.Port)
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the tunnel's options
|
||||||
|
func (h *HTTPClient) SetOptions(opts map[string]string) error {
|
||||||
|
// Apply configuration options from the map
|
||||||
|
if name, ok := opts["name"]; ok {
|
||||||
|
h.TunnelConfig.Name = name
|
||||||
|
}
|
||||||
|
if iface, ok := opts["interface"]; ok {
|
||||||
|
h.TunnelConfig.Interface = iface
|
||||||
|
}
|
||||||
|
if portStr, ok := opts["port"]; ok {
|
||||||
|
if port, err := strconv.Atoi(portStr); err == nil {
|
||||||
|
h.TunnelConfig.Port = port
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid port value: %s", portStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the tunnel config from file
|
||||||
|
func (h *HTTPClient) LoadConfig(path string) error {
|
||||||
|
// For now, return an error indicating this method needs configuration file support
|
||||||
|
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ func NewHTTPClient(config i2pconv.TunnelConfig, samAddr string) (*HTTPClient, er
|
|||||||
I2PTunnelStatus: i2ptunnel.I2PTunnelStatusStopped,
|
I2PTunnelStatus: i2ptunnel.I2PTunnelStatusStopped,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Key features:
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -64,7 +65,11 @@ func (h *HTTPServer) recordError(err error) {
|
|||||||
|
|
||||||
// Get the tunnel's I2P address
|
// Get the tunnel's I2P address
|
||||||
func (h *HTTPServer) Address() string {
|
func (h *HTTPServer) Address() string {
|
||||||
return h.Garlic.B32()
|
// For HTTP server, return the service address if available
|
||||||
|
if h.Garlic != nil && h.Garlic.ServiceKeys != nil {
|
||||||
|
return h.Garlic.ServiceKeys.Addr().Base32()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the tunnel's error message
|
// Get the tunnel's error message
|
||||||
@@ -141,3 +146,63 @@ func (h *HTTPServer) Target() string {
|
|||||||
func (h *HTTPServer) Type() string {
|
func (h *HTTPServer) Type() string {
|
||||||
return h.TunnelConfig.Type
|
return h.TunnelConfig.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's ID
|
||||||
|
func (h *HTTPServer) ID() string {
|
||||||
|
return i2ptunnel.Clean(h.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's options
|
||||||
|
func (h *HTTPServer) Options() map[string]string {
|
||||||
|
// Return basic configuration options as a map
|
||||||
|
options := make(map[string]string)
|
||||||
|
options["name"] = h.TunnelConfig.Name
|
||||||
|
options["type"] = h.TunnelConfig.Type
|
||||||
|
options["interface"] = h.TunnelConfig.Interface
|
||||||
|
options["port"] = strconv.Itoa(h.TunnelConfig.Port)
|
||||||
|
options["maxconns"] = strconv.Itoa(h.LimitedConfig.MaxConns)
|
||||||
|
options["ratelimit"] = strconv.FormatFloat(h.LimitedConfig.RateLimit, 'f', -1, 64)
|
||||||
|
if h.Addr != nil {
|
||||||
|
options["target"] = h.Addr.String()
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the tunnel's options
|
||||||
|
func (h *HTTPServer) SetOptions(opts map[string]string) error {
|
||||||
|
// Apply configuration options from the map
|
||||||
|
if name, ok := opts["name"]; ok {
|
||||||
|
h.TunnelConfig.Name = name
|
||||||
|
}
|
||||||
|
if iface, ok := opts["interface"]; ok {
|
||||||
|
h.TunnelConfig.Interface = iface
|
||||||
|
}
|
||||||
|
if portStr, ok := opts["port"]; ok {
|
||||||
|
if port, err := strconv.Atoi(portStr); err == nil {
|
||||||
|
h.TunnelConfig.Port = port
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid port value: %s", portStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxconnsStr, ok := opts["maxconns"]; ok {
|
||||||
|
if maxconns, err := strconv.Atoi(maxconnsStr); err == nil {
|
||||||
|
h.LimitedConfig.MaxConns = maxconns
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid maxconns value: %s", maxconnsStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ratelimitStr, ok := opts["ratelimit"]; ok {
|
||||||
|
if ratelimit, err := strconv.ParseFloat(ratelimitStr, 64); err == nil {
|
||||||
|
h.LimitedConfig.RateLimit = ratelimit
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid ratelimit value: %s", ratelimitStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the tunnel config from file
|
||||||
|
func (h *HTTPServer) LoadConfig(path string) error {
|
||||||
|
// For now, return an error indicating this method needs configuration file support
|
||||||
|
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ When a local client connects to the I2P tunnel's destination, the traffic flows:
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -57,7 +58,11 @@ func (t *TCPClient) recordError(err error) {
|
|||||||
|
|
||||||
// Get the tunnel's I2P address
|
// Get the tunnel's I2P address
|
||||||
func (t *TCPClient) Address() string {
|
func (t *TCPClient) Address() string {
|
||||||
return t.Garlic.StreamSession.Addr().Base32()
|
// Return the target I2P address for client tunnels
|
||||||
|
if t.I2PAddr != nil {
|
||||||
|
return t.I2PAddr.Base32()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the tunnel's error message
|
// Get the tunnel's error message
|
||||||
@@ -132,3 +137,54 @@ func (t *TCPClient) Target() string {
|
|||||||
func (t *TCPClient) Type() string {
|
func (t *TCPClient) Type() string {
|
||||||
return t.TunnelConfig.Type
|
return t.TunnelConfig.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's ID
|
||||||
|
func (t *TCPClient) ID() string {
|
||||||
|
return i2ptunnel.Clean(t.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's options
|
||||||
|
func (t *TCPClient) Options() map[string]string {
|
||||||
|
// Return basic configuration options as a map
|
||||||
|
options := make(map[string]string)
|
||||||
|
options["name"] = t.TunnelConfig.Name
|
||||||
|
options["type"] = t.TunnelConfig.Type
|
||||||
|
options["interface"] = t.TunnelConfig.Interface
|
||||||
|
options["port"] = strconv.Itoa(t.TunnelConfig.Port)
|
||||||
|
if t.I2PAddr != nil {
|
||||||
|
options["target"] = t.I2PAddr.Base32()
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the tunnel's options
|
||||||
|
func (t *TCPClient) SetOptions(opts map[string]string) error {
|
||||||
|
// Apply configuration options from the map
|
||||||
|
if name, ok := opts["name"]; ok {
|
||||||
|
t.TunnelConfig.Name = name
|
||||||
|
}
|
||||||
|
if iface, ok := opts["interface"]; ok {
|
||||||
|
t.TunnelConfig.Interface = iface
|
||||||
|
}
|
||||||
|
if portStr, ok := opts["port"]; ok {
|
||||||
|
if port, err := strconv.Atoi(portStr); err == nil {
|
||||||
|
t.TunnelConfig.Port = port
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid port value: %s", portStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target, ok := opts["target"]; ok {
|
||||||
|
addr, err := i2pkeys.Lookup(target)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid target address: %w", err)
|
||||||
|
}
|
||||||
|
t.I2PAddr = addr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the tunnel config from file
|
||||||
|
func (t *TCPClient) LoadConfig(path string) error {
|
||||||
|
// For now, return an error indicating this method needs configuration file support
|
||||||
|
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ When an I2P peer connects to the tunnel's destination, the traffic flows:
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -52,8 +53,14 @@ func (t *TCPServer) recordError(err error) {
|
|||||||
|
|
||||||
// Get the tunnel's I2P address
|
// Get the tunnel's I2P address
|
||||||
func (t *TCPServer) Address() string {
|
func (t *TCPServer) Address() string {
|
||||||
return t.Garlic.StreamListener.Addr().String()
|
// For server tunnels, return the service address if available
|
||||||
//B32()
|
if t.Garlic != nil {
|
||||||
|
// Use service keys to identify the tunnel's I2P address
|
||||||
|
if t.Garlic.ServiceKeys != nil {
|
||||||
|
return t.Garlic.ServiceKeys.Addr().Base32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the tunnel's error message
|
// Get the tunnel's error message
|
||||||
@@ -129,3 +136,63 @@ func (t *TCPServer) Target() string {
|
|||||||
func (t *TCPServer) Type() string {
|
func (t *TCPServer) Type() string {
|
||||||
return t.TunnelConfig.Type
|
return t.TunnelConfig.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's ID
|
||||||
|
func (t *TCPServer) ID() string {
|
||||||
|
return i2ptunnel.Clean(t.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the tunnel's options
|
||||||
|
func (t *TCPServer) Options() map[string]string {
|
||||||
|
// Return basic configuration options as a map
|
||||||
|
options := make(map[string]string)
|
||||||
|
options["name"] = t.TunnelConfig.Name
|
||||||
|
options["type"] = t.TunnelConfig.Type
|
||||||
|
options["interface"] = t.TunnelConfig.Interface
|
||||||
|
options["port"] = strconv.Itoa(t.TunnelConfig.Port)
|
||||||
|
options["maxconns"] = strconv.Itoa(t.LimitedConfig.MaxConns)
|
||||||
|
options["ratelimit"] = strconv.FormatFloat(t.LimitedConfig.RateLimit, 'f', -1, 64)
|
||||||
|
if t.Addr != nil {
|
||||||
|
options["target"] = t.Addr.String()
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the tunnel's options
|
||||||
|
func (t *TCPServer) SetOptions(opts map[string]string) error {
|
||||||
|
// Apply configuration options from the map
|
||||||
|
if name, ok := opts["name"]; ok {
|
||||||
|
t.TunnelConfig.Name = name
|
||||||
|
}
|
||||||
|
if iface, ok := opts["interface"]; ok {
|
||||||
|
t.TunnelConfig.Interface = iface
|
||||||
|
}
|
||||||
|
if portStr, ok := opts["port"]; ok {
|
||||||
|
if port, err := strconv.Atoi(portStr); err == nil {
|
||||||
|
t.TunnelConfig.Port = port
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid port value: %s", portStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxconnsStr, ok := opts["maxconns"]; ok {
|
||||||
|
if maxconns, err := strconv.Atoi(maxconnsStr); err == nil {
|
||||||
|
t.LimitedConfig.MaxConns = maxconns
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid maxconns value: %s", maxconnsStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ratelimitStr, ok := opts["ratelimit"]; ok {
|
||||||
|
if ratelimit, err := strconv.ParseFloat(ratelimitStr, 64); err == nil {
|
||||||
|
t.LimitedConfig.RateLimit = ratelimit
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid ratelimit value: %s", ratelimitStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the tunnel config from file
|
||||||
|
func (t *TCPServer) LoadConfig(path string) error {
|
||||||
|
// For now, return an error indicating this method needs configuration file support
|
||||||
|
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package tcp
|
package tcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -36,15 +35,19 @@ func RandomUDPPort() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTCPTunnel(t *testing.T) {
|
func TestTCPTunnel(t *testing.T) {
|
||||||
|
t.Log("Starting TCP tunnel test")
|
||||||
|
|
||||||
// Generate test keys
|
// Generate test keys
|
||||||
//keys, err := i2pkeys.LoadKeys("i2pkeys/test-server.i2p.private")
|
t.Log("Loading test keys...")
|
||||||
_, err := i2pkeys.LoadKeys("i2pkeys/test-server.i2p.private")
|
_, err := i2pkeys.LoadKeys("i2pkeys/test-server.i2p.private")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to load test keys: %v", err)
|
t.Fatalf("Failed to load test keys: %v", err)
|
||||||
}
|
}
|
||||||
sport := RandomTCPPort()
|
sport := RandomTCPPort()
|
||||||
|
t.Logf("Selected server port: %d", sport)
|
||||||
|
|
||||||
// Setup server config
|
// Setup server config
|
||||||
|
t.Log("Setting up server configuration...")
|
||||||
serverConfig := i2pconv.TunnelConfig{
|
serverConfig := i2pconv.TunnelConfig{
|
||||||
Name: "test-server",
|
Name: "test-server",
|
||||||
Type: "tcpserver",
|
Type: "tcpserver",
|
||||||
@@ -53,11 +56,13 @@ func TestTCPTunnel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create and start server
|
// Create and start server
|
||||||
|
t.Log("Creating TCP server...")
|
||||||
srv, err := server.NewTCPServer(serverConfig, "127.0.0.1:7656")
|
srv, err := server.NewTCPServer(serverConfig, "127.0.0.1:7656")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create server: %v", err)
|
t.Fatalf("Failed to create server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Log("Starting TCP server...")
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.Start(); err != nil {
|
if err := srv.Start(); err != nil {
|
||||||
t.Errorf("Server error: %v", err)
|
t.Errorf("Server error: %v", err)
|
||||||
@@ -65,10 +70,13 @@ func TestTCPTunnel(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
defer srv.Stop()
|
defer srv.Stop()
|
||||||
cport := RandomTCPPort()
|
cport := RandomTCPPort()
|
||||||
// Wait for server startup
|
t.Logf("Selected client port: %d", cport)
|
||||||
|
|
||||||
|
t.Log("Waiting for server startup...")
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Setup client config
|
// Setup client config
|
||||||
|
t.Log("Setting up client configuration...")
|
||||||
clientConfig := i2pconv.TunnelConfig{
|
clientConfig := i2pconv.TunnelConfig{
|
||||||
Name: "test-client",
|
Name: "test-client",
|
||||||
Type: "tcpclient",
|
Type: "tcpclient",
|
||||||
@@ -78,11 +86,13 @@ func TestTCPTunnel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create and start client
|
// Create and start client
|
||||||
|
t.Log("Creating TCP client...")
|
||||||
cli, err := client.NewTCPClient(clientConfig, "127.0.0.1:7656")
|
cli, err := client.NewTCPClient(clientConfig, "127.0.0.1:7656")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create client: %v", err)
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Log("Starting TCP client...")
|
||||||
go func() {
|
go func() {
|
||||||
if err := cli.Start(); err != nil {
|
if err := cli.Start(); err != nil {
|
||||||
t.Errorf("Client error: %v", err)
|
t.Errorf("Client error: %v", err)
|
||||||
@@ -90,29 +100,39 @@ func TestTCPTunnel(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
defer cli.Stop()
|
defer cli.Stop()
|
||||||
|
|
||||||
// Wait for client startup
|
t.Log("Waiting for client startup...")
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Test data transfer
|
// Test data transfer
|
||||||
testData := []byte("Hello I2P!")
|
testData := []byte("Hello I2P!")
|
||||||
|
t.Log("Attempting to establish connection...")
|
||||||
conn, err := net.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(cport)))
|
conn, err := net.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(cport)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to connect: %v", err)
|
t.Fatalf("Failed to connect: %v", err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
t.Log("Connection established successfully")
|
||||||
|
|
||||||
|
t.Logf("Writing test data: %q", testData)
|
||||||
n, err := conn.Write(testData)
|
n, err := conn.Write(testData)
|
||||||
if err != nil || n != len(testData) {
|
if err != nil || n != len(testData) {
|
||||||
t.Fatalf("Failed to write test data: %v", err)
|
t.Fatalf("Failed to write test data: %v", err)
|
||||||
}
|
}
|
||||||
|
t.Logf("Successfully wrote %d bytes", n)
|
||||||
|
|
||||||
|
t.Log("Reading response...")
|
||||||
buf := make([]byte, len(testData))
|
buf := make([]byte, len(testData))
|
||||||
n, err = io.ReadFull(conn, buf)
|
n, err = conn.Read(buf)
|
||||||
if err != nil || n != len(testData) {
|
if err != nil {
|
||||||
t.Fatalf("Failed to read test data: %v", err)
|
t.Fatalf("Failed to read test data: %v", err)
|
||||||
}
|
}
|
||||||
|
if n != len(testData) {
|
||||||
|
t.Fatalf("Read wrong number of bytes: got %d, want %d", n, len(testData))
|
||||||
|
}
|
||||||
|
t.Logf("Successfully read %d bytes", n)
|
||||||
|
|
||||||
if string(buf) != string(testData) {
|
if string(buf) != string(testData) {
|
||||||
t.Errorf("Data mismatch: got %q, want %q", buf, testData)
|
t.Errorf("Data mismatch: got %q, want %q", buf, testData)
|
||||||
}
|
}
|
||||||
|
t.Log("Test completed successfully")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user