mirror of
https://github.com/go-i2p/go-sam-go.git
synced 2025-12-01 09:54:58 -05:00
650 lines
18 KiB
Go
650 lines
18 KiB
Go
package sam3
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestSAMSessionMethods tests that all session creation methods work with real I2P connections.
|
|
// These are integration tests that require a running I2P router with SAM bridge enabled.
|
|
func TestSAMSessionMethods(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration tests in short mode")
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
methodTest func(*testing.T)
|
|
description string
|
|
}{
|
|
{
|
|
name: "NewPrimarySession",
|
|
methodTest: func(t *testing.T) {
|
|
// Create a separate SAM connection for this test
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Generate real I2P keys for testing
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewPrimarySession("test-primary-"+RandString(), keys, Options_Default)
|
|
if err != nil {
|
|
t.Errorf("NewPrimarySession failed: %v", err)
|
|
return
|
|
}
|
|
defer session.Close()
|
|
|
|
// Verify we can create sub-sessions
|
|
if session.SubSessionCount() != 0 {
|
|
t.Errorf("Expected 0 sub-sessions, got %d", session.SubSessionCount())
|
|
}
|
|
},
|
|
description: "Primary session creation and basic operations",
|
|
},
|
|
{
|
|
name: "NewPrimarySessionWithSignature",
|
|
methodTest: func(t *testing.T) {
|
|
// Create a separate SAM connection for this test
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Generate real I2P keys for testing
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewPrimarySessionWithSignature("test-primary-sig-"+RandString(), keys, Options_Default, Sig_EdDSA_SHA512_Ed25519)
|
|
if err != nil {
|
|
t.Errorf("NewPrimarySessionWithSignature failed: %v", err)
|
|
return
|
|
}
|
|
defer session.Close()
|
|
|
|
// Verify session properties
|
|
if session.Addr().String() == "" {
|
|
t.Error("Expected non-empty session address")
|
|
}
|
|
},
|
|
description: "Primary session creation with signature type",
|
|
},
|
|
{
|
|
name: "NewStreamSession",
|
|
methodTest: func(t *testing.T) {
|
|
// Create a separate SAM connection for this test
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Generate real I2P keys for testing
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewStreamSession("test-stream-"+RandString(), keys, Options_Default)
|
|
if err != nil {
|
|
t.Errorf("NewStreamSession failed: %v", err)
|
|
return
|
|
}
|
|
defer session.Close()
|
|
|
|
// Verify session has valid address
|
|
if session.Addr().String() == "" {
|
|
t.Error("Expected non-empty stream session address")
|
|
}
|
|
},
|
|
description: "Stream session creation",
|
|
},
|
|
{
|
|
name: "NewStreamSessionWithSignature",
|
|
methodTest: func(t *testing.T) {
|
|
// Create a separate SAM connection for this test
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Generate real I2P keys for testing
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewStreamSessionWithSignature("test-stream-sig-"+RandString(), keys, Options_Default, Sig_EdDSA_SHA512_Ed25519)
|
|
if err != nil {
|
|
t.Errorf("NewStreamSessionWithSignature failed: %v", err)
|
|
return
|
|
}
|
|
defer session.Close()
|
|
|
|
// Test that we can create a listener
|
|
listener, err := session.Listen()
|
|
if err != nil {
|
|
t.Errorf("Failed to create listener: %v", err)
|
|
return
|
|
}
|
|
defer listener.Close()
|
|
|
|
if listener.Addr().String() == "" {
|
|
t.Error("Expected non-empty listener address")
|
|
}
|
|
},
|
|
description: "Stream session with signature and listener creation",
|
|
},
|
|
{
|
|
name: "NewStreamSessionWithSignatureAndPorts",
|
|
methodTest: func(t *testing.T) {
|
|
// Create a separate SAM connection for this test
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Generate real I2P keys for testing
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewStreamSessionWithSignatureAndPorts("test-stream-ports-"+RandString(), "0", "0", keys, Options_Default, Sig_EdDSA_SHA512_Ed25519)
|
|
if err != nil {
|
|
t.Errorf("NewStreamSessionWithSignatureAndPorts failed: %v", err)
|
|
return
|
|
}
|
|
defer session.Close()
|
|
|
|
// Test that we can create a listener
|
|
listener, err := session.Listen()
|
|
if err != nil {
|
|
t.Errorf("Failed to create listener with ports: %v", err)
|
|
return
|
|
}
|
|
defer listener.Close()
|
|
|
|
if listener.Addr().String() == "" {
|
|
t.Error("Expected non-empty listener address")
|
|
}
|
|
},
|
|
description: "Stream session with signature, ports, and listener creation",
|
|
},
|
|
{
|
|
name: "NewDatagramSession",
|
|
methodTest: func(t *testing.T) {
|
|
// Create a separate SAM connection for this test
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Generate real I2P keys for testing
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewDatagramSession("test-datagram-"+RandString(), keys, Options_Default)
|
|
if err != nil {
|
|
t.Errorf("NewDatagramSession failed: %v", err)
|
|
return
|
|
}
|
|
defer session.Close()
|
|
|
|
// Verify session properties
|
|
if session.Addr().String() == "" {
|
|
t.Error("Expected non-empty datagram session address")
|
|
}
|
|
},
|
|
description: "Datagram session creation",
|
|
},
|
|
{
|
|
name: "NewRawSession",
|
|
methodTest: func(t *testing.T) {
|
|
// Create a separate SAM connection for this test
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Generate real I2P keys for testing
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewRawSession("test-raw-"+RandString(), keys, Options_Default, 0)
|
|
if err != nil {
|
|
t.Errorf("NewRawSession failed: %v", err)
|
|
return
|
|
}
|
|
defer session.Close()
|
|
|
|
// Verify session properties
|
|
if session.Addr().String() == "" {
|
|
t.Error("Expected non-empty raw session address")
|
|
}
|
|
},
|
|
description: "Raw session creation",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Set I2P-appropriate timeout for session creation
|
|
// I2P operations can take several minutes due to tunnel building
|
|
done := make(chan bool, 1)
|
|
go func() {
|
|
tc.methodTest(t)
|
|
done <- true
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
t.Logf("✓ %s: %s", tc.name, tc.description)
|
|
case <-time.After(5 * time.Minute):
|
|
t.Errorf("%s timed out after 5 minutes (I2P tunnel building can be slow)", tc.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPrimarySessionSubSessions tests that primary sessions can create and manage sub-sessions
|
|
func TestPrimarySessionSubSessions(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration tests in short mode")
|
|
}
|
|
|
|
// Create a real SAM connection
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Create primary session
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate I2P keys: %v", err)
|
|
}
|
|
|
|
primary, err := sam.NewPrimarySession("test-primary-subsessions-"+RandString(), keys, Options_Default)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create primary session: %v", err)
|
|
}
|
|
defer primary.Close()
|
|
|
|
// Test sub-session creation sequentially to ensure proper session lifecycle
|
|
// I2P session operations are inherently time-intensive (1-5 minutes each)
|
|
// so parallel execution provides minimal benefit while introducing race conditions
|
|
t.Run("StreamSubSession", func(t *testing.T) {
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
streamSub, err := primary.NewStreamSubSession("stream-sub-"+RandString(), Options_Small)
|
|
if err != nil {
|
|
done <- err
|
|
return
|
|
}
|
|
defer streamSub.Close()
|
|
|
|
if streamSub.ID() == "" {
|
|
t.Error("Expected non-empty sub-session ID")
|
|
done <- fmt.Errorf("Expected non-empty sub-session ID")
|
|
return
|
|
}
|
|
|
|
if streamSub.Type() != "STREAM" {
|
|
t.Errorf("Expected STREAM type, got %s", streamSub.Type())
|
|
done <- fmt.Errorf("Expected STREAM type, got %s", streamSub.Type())
|
|
return
|
|
}
|
|
|
|
done <- nil
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Errorf("Stream sub-session creation failed: %v", err)
|
|
}
|
|
case <-time.After(3 * time.Minute):
|
|
t.Error("Stream sub-session creation timed out")
|
|
}
|
|
})
|
|
|
|
t.Run("DatagramSubSession", func(t *testing.T) {
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
// DATAGRAM subsessions require a PORT parameter per SAM v3.3 specification
|
|
options := append(Options_Small, "PORT=8080")
|
|
datagramSub, err := primary.NewDatagramSubSession("datagram-sub-"+RandString(), options)
|
|
if err != nil {
|
|
done <- err
|
|
return
|
|
}
|
|
defer datagramSub.Close()
|
|
|
|
if datagramSub.Type() != "DATAGRAM" {
|
|
t.Errorf("Expected DATAGRAM type, got %s", datagramSub.Type())
|
|
done <- fmt.Errorf("Expected DATAGRAM type, got %s", datagramSub.Type())
|
|
return
|
|
}
|
|
|
|
done <- nil
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Errorf("Datagram sub-session creation failed: %v", err)
|
|
}
|
|
case <-time.After(3 * time.Minute):
|
|
t.Error("Datagram sub-session creation timed out")
|
|
}
|
|
})
|
|
|
|
t.Run("RawSubSession", func(t *testing.T) {
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
// RAW subsessions require a PORT parameter per SAM v3.3 specification
|
|
options := append(Options_Small, "PORT=8081")
|
|
rawSub, err := primary.NewRawSubSession("raw-sub-"+RandString(), options)
|
|
if err != nil {
|
|
done <- err
|
|
return
|
|
}
|
|
defer rawSub.Close()
|
|
|
|
if rawSub.Type() != "RAW" {
|
|
t.Errorf("Expected RAW type, got %s", rawSub.Type())
|
|
done <- fmt.Errorf("Expected RAW type, got %s", rawSub.Type())
|
|
return
|
|
}
|
|
|
|
done <- nil
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Errorf("Raw sub-session creation failed: %v", err)
|
|
}
|
|
case <-time.After(3 * time.Minute):
|
|
t.Error("Raw sub-session creation timed out")
|
|
}
|
|
})
|
|
|
|
// Wait for all sub-tests to complete before checking sub-session count
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Verify sub-session management
|
|
if primary.SubSessionCount() < 0 {
|
|
t.Errorf("Expected non-negative sub-session count, got %d", primary.SubSessionCount())
|
|
}
|
|
}
|
|
|
|
// TestSAMEmbedding tests that the SAM type properly embeds common.SAM
|
|
// and provides access to the underlying functionality with real connections.
|
|
func TestSAMEmbedding(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration tests in short mode")
|
|
}
|
|
|
|
// Create a real SAM connection
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
// Verify that we can access embedded methods
|
|
if sam.SAM == nil {
|
|
t.Fatal("SAM should embed common.SAM")
|
|
}
|
|
|
|
// Test embedded functionality
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys using embedded SAM: %v", err)
|
|
}
|
|
|
|
if keys.Addr().String() == "" {
|
|
t.Error("Generated keys should have non-empty address")
|
|
}
|
|
|
|
// Test resolver functionality
|
|
resolver, err := NewSAMResolver(sam)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create resolver: %v", err)
|
|
}
|
|
|
|
// Test that resolver can be used (though we don't test actual resolution
|
|
// since that requires specific I2P destinations to be available)
|
|
if resolver == nil {
|
|
t.Error("Resolver should not be nil")
|
|
}
|
|
}
|
|
|
|
// TestSessionMethodSignatures verifies that the session creation methods
|
|
// have the exact signatures expected by the sam3 API using real connections.
|
|
func TestSessionMethodSignatures(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration tests in short mode")
|
|
}
|
|
|
|
// Test each method signature by actually calling them
|
|
// This is more comprehensive than just compile-time checks
|
|
|
|
// NewPrimarySession signature test
|
|
t.Run("NewPrimarySession", func(t *testing.T) {
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewPrimarySession("sig-test-primary-"+RandString(), keys, Options_Small)
|
|
if err != nil {
|
|
t.Errorf("NewPrimarySession signature test failed: %v", err)
|
|
} else {
|
|
session.Close()
|
|
t.Log("✓ NewPrimarySession signature works correctly")
|
|
}
|
|
})
|
|
|
|
// NewPrimarySessionWithSignature signature test
|
|
t.Run("NewPrimarySessionWithSignature", func(t *testing.T) {
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewPrimarySessionWithSignature("sig-test-primary-sig-"+RandString(), keys, Options_Small, Sig_EdDSA_SHA512_Ed25519)
|
|
if err != nil {
|
|
t.Errorf("NewPrimarySessionWithSignature signature test failed: %v", err)
|
|
} else {
|
|
session.Close()
|
|
t.Log("✓ NewPrimarySessionWithSignature signature works correctly")
|
|
}
|
|
})
|
|
|
|
// NewStreamSession signature test
|
|
t.Run("NewStreamSession", func(t *testing.T) {
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewStreamSession("sig-test-stream-"+RandString(), keys, Options_Small)
|
|
if err != nil {
|
|
t.Errorf("NewStreamSession signature test failed: %v", err)
|
|
} else {
|
|
session.Close()
|
|
t.Log("✓ NewStreamSession signature works correctly")
|
|
}
|
|
})
|
|
|
|
// NewStreamSessionWithSignature signature test
|
|
t.Run("NewStreamSessionWithSignature", func(t *testing.T) {
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewStreamSessionWithSignature("sig-test-stream-sig-"+RandString(), keys, Options_Small, Sig_EdDSA_SHA512_Ed25519)
|
|
if err != nil {
|
|
t.Errorf("NewStreamSessionWithSignature signature test failed: %v", err)
|
|
} else {
|
|
session.Close()
|
|
t.Log("✓ NewStreamSessionWithSignature signature works correctly")
|
|
}
|
|
})
|
|
|
|
// NewStreamSessionWithSignatureAndPorts test
|
|
t.Run("NewStreamSessionWithSignatureAndPorts", func(t *testing.T) {
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewStreamSessionWithSignatureAndPorts("ports-test-stream-"+RandString(), "0", "0", keys, Options_Small, Sig_EdDSA_SHA512_Ed25519)
|
|
if err != nil {
|
|
t.Errorf("NewStreamSessionWithSignatureAndPorts test failed: %v", err)
|
|
} else {
|
|
session.Close()
|
|
t.Log("✓ NewStreamSessionWithSignatureAndPorts works correctly")
|
|
}
|
|
})
|
|
|
|
// NewDatagramSession signature test
|
|
t.Run("NewDatagramSession", func(t *testing.T) {
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewDatagramSession("sig-test-datagram-"+RandString(), keys, Options_Small)
|
|
if err != nil {
|
|
t.Errorf("NewDatagramSession signature test failed: %v", err)
|
|
} else {
|
|
session.Close()
|
|
t.Log("✓ NewDatagramSession signature works correctly")
|
|
}
|
|
})
|
|
|
|
// NewRawSession signature test
|
|
t.Run("NewRawSession", func(t *testing.T) {
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
session, err := sam.NewRawSession("sig-test-raw-"+RandString(), keys, Options_Small, 0)
|
|
if err != nil {
|
|
t.Errorf("NewRawSession signature test failed: %v", err)
|
|
} else {
|
|
session.Close()
|
|
t.Log("✓ NewRawSession signature works correctly")
|
|
}
|
|
})
|
|
|
|
t.Log("✓ All session method signatures work correctly with real I2P connections")
|
|
}
|
|
|
|
// BenchmarkSAMSessionCreation benchmarks the performance of session creation with real I2P connections
|
|
func BenchmarkSAMSessionCreation(b *testing.B) {
|
|
if testing.Short() {
|
|
b.Skip("Skipping benchmarks in short mode")
|
|
}
|
|
|
|
// Create a real SAM connection
|
|
sam, err := NewSAM(SAMDefaultAddr(""))
|
|
if err != nil {
|
|
b.Skipf("Cannot connect to I2P SAM bridge: %v", err)
|
|
}
|
|
defer sam.Close()
|
|
|
|
keys, err := sam.NewKeys()
|
|
if err != nil {
|
|
b.Fatalf("Failed to generate keys: %v", err)
|
|
}
|
|
|
|
b.Run("PrimarySession", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
session, err := sam.NewPrimarySession("bench-primary-"+RandString(), keys, Options_Small)
|
|
if err != nil {
|
|
b.Errorf("Primary session creation failed: %v", err)
|
|
continue
|
|
}
|
|
session.Close()
|
|
}
|
|
})
|
|
|
|
b.Run("StreamSession", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
session, err := sam.NewStreamSession("bench-stream-"+RandString(), keys, Options_Small)
|
|
if err != nil {
|
|
b.Errorf("Stream session creation failed: %v", err)
|
|
continue
|
|
}
|
|
session.Close()
|
|
}
|
|
})
|
|
}
|