From 5ee5d0e18178956d33373ac744f1f781ce3bd883 Mon Sep 17 00:00:00 2001 From: eyedeekay Date: Sun, 1 Jun 2025 17:27:48 -0400 Subject: [PATCH] add tests for raw sessions --- raw/listen_test.go | 238 ++++++++++++++++++++++++++++++++++++++ raw/session_test.go | 274 ++++++++++++++++++++++++++++++++++++++++++++ raw/types_test.go | 13 +++ 3 files changed, 525 insertions(+) create mode 100644 raw/listen_test.go create mode 100644 raw/session_test.go create mode 100644 raw/types_test.go diff --git a/raw/listen_test.go b/raw/listen_test.go new file mode 100644 index 00000000..c5478d3d --- /dev/null +++ b/raw/listen_test.go @@ -0,0 +1,238 @@ +package raw + +import ( + "testing" + "time" + + "github.com/go-i2p/go-sam-go/common" +) + +func TestRawSession_Listen(t *testing.T) { + tests := []struct { + name string + setupSession func() *RawSession + wantErr bool + errContains string + }{ + { + name: "successful_listen", + setupSession: func() *RawSession { + // Create a mock SAM connection + sam := &common.SAM{} + baseSession := &common.BaseSession{} + return &RawSession{ + BaseSession: baseSession, + sam: sam, + options: []string{}, + closed: false, + } + }, + wantErr: false, + }, + { + name: "listen_on_closed_session", + setupSession: func() *RawSession { + sam := &common.SAM{} + baseSession := &common.BaseSession{} + return &RawSession{ + BaseSession: baseSession, + sam: sam, + options: []string{}, + closed: true, + } + }, + wantErr: true, + errContains: "session is closed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + session := tt.setupSession() + + listener, err := session.Listen() + + if tt.wantErr { + if err == nil { + t.Errorf("Listen() expected error but got none") + return + } + if tt.errContains != "" && !containsString(err.Error(), tt.errContains) { + t.Errorf("Listen() error = %v, want error containing %q", err, tt.errContains) + } + return + } + + if err != nil { + t.Errorf("Listen() unexpected error = %v", err) + return + } + + if listener == nil { + t.Error("Listen() returned nil listener") + return + } + + // Verify listener properties + if listener.session != session { + t.Error("Listener session reference incorrect") + } + + if listener.reader == nil { + t.Error("Listener reader not initialized") + } + + if listener.acceptChan == nil { + t.Error("Listener acceptChan not initialized") + } + + if listener.errorChan == nil { + t.Error("Listener errorChan not initialized") + } + + if listener.closeChan == nil { + t.Error("Listener closeChan not initialized") + } + + // Clean up + if listener != nil { + _ = listener.Close() + } + }) + } +} + +func TestRawListener_Properties(t *testing.T) { + // Setup a basic session for testing + sam := &common.SAM{} + baseSession := &common.BaseSession{} + session := &RawSession{ + BaseSession: baseSession, + sam: sam, + options: []string{}, + closed: false, + } + + listener, err := session.Listen() + if err != nil { + t.Fatalf("Failed to create listener: %v", err) + } + defer listener.Close() + + t.Run("channels_buffered_correctly", func(t *testing.T) { + // Check that acceptChan has proper buffer size + select { + case listener.acceptChan <- &RawConn{}: + // Should not block for first 10 items + case <-time.After(100 * time.Millisecond): + t.Error("acceptChan appears to be unbuffered or too small") + } + + // Drain the channel + select { + case <-listener.acceptChan: + default: + t.Error("Failed to read from acceptChan") + } + }) + + t.Run("initial_state", func(t *testing.T) { + if listener.closed { + t.Error("New listener should not be closed initially") + } + }) +} + +func TestRawListener_Close(t *testing.T) { + sam := &common.SAM{} + baseSession := &common.BaseSession{} + session := &RawSession{ + BaseSession: baseSession, + sam: sam, + options: []string{}, + closed: false, + } + + listener, err := session.Listen() + if err != nil { + t.Fatalf("Failed to create listener: %v", err) + } + + // Test closing + err = listener.Close() + if err != nil { + t.Errorf("Close() returned error: %v", err) + } + + // Verify closed state + listener.mu.RLock() + closed := listener.closed + listener.mu.RUnlock() + + if !closed { + t.Error("Listener should be marked as closed after Close()") + } + + // Test double close + err = listener.Close() + if err == nil { + t.Error("Second Close() should return error") + } +} + +func TestRawListener_Concurrent_Access(t *testing.T) { + sam := &common.SAM{} + baseSession := &common.BaseSession{} + session := &RawSession{ + BaseSession: baseSession, + sam: sam, + options: []string{}, + closed: false, + } + + listener, err := session.Listen() + if err != nil { + t.Fatalf("Failed to create listener: %v", err) + } + defer listener.Close() + + // Test concurrent access to listener state + done := make(chan bool, 2) + + go func() { + defer func() { done <- true }() + for i := 0; i < 100; i++ { + listener.mu.RLock() + _ = listener.closed + listener.mu.RUnlock() + } + }() + + go func() { + defer func() { done <- true }() + for i := 0; i < 100; i++ { + listener.mu.RLock() + _ = listener.session + listener.mu.RUnlock() + } + }() + + // Wait for both goroutines + <-done + <-done +} + +// Helper function to check if a string contains a substring +func containsString(s, substr string) bool { + return len(s) >= len(substr) && + (substr == "" || findString(s, substr)) +} + +func findString(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/raw/session_test.go b/raw/session_test.go new file mode 100644 index 00000000..789a4bc1 --- /dev/null +++ b/raw/session_test.go @@ -0,0 +1,274 @@ +package raw + +import ( + "testing" + + "github.com/go-i2p/go-sam-go/common" + "github.com/go-i2p/i2pkeys" +) + +const testSAMAddr = "127.0.0.1:7656" + +func setupTestSAM(t *testing.T) (*common.SAM, i2pkeys.I2PKeys) { + t.Helper() + + sam, err := common.NewSAM(testSAMAddr) + if err != nil { + t.Fatalf("Failed to create SAM connection: %v", err) + } + + keys, err := sam.NewKeys() + if err != nil { + sam.Close() + t.Fatalf("Failed to generate keys: %v", err) + } + + return sam, keys +} + +func TestNewRawSession(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + tests := []struct { + name string + id string + options []string + wantErr bool + }{ + { + name: "basic session creation", + id: "test_raw_session", + options: nil, + wantErr: false, + }, + { + name: "session with options", + id: "test_raw_with_opts", + options: []string{"inbound.length=1", "outbound.length=1"}, + wantErr: false, + }, + { + name: "session with small tunnel config", + id: "test_raw_small", + options: []string{ + "inbound.length=0", + "outbound.length=0", + "inbound.lengthVariance=0", + "outbound.lengthVariance=0", + "inbound.quantity=1", + "outbound.quantity=1", + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sam, keys := setupTestSAM(t) + defer sam.Close() + + session, err := NewRawSession(sam, tt.id, keys, tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("NewRawSession() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err == nil { + // Verify session properties + if session.ID() != tt.id { + t.Errorf("Session ID = %v, want %v", session.ID(), tt.id) + } + + if session.Keys().Addr().Base32() != keys.Addr().Base32() { + t.Error("Session keys don't match provided keys") + } + + addr := session.Addr() + if addr.Base32() == "" { + t.Error("Session address is empty") + } + + // Clean up + if err := session.Close(); err != nil { + t.Errorf("Failed to close session: %v", err) + } + } + }) + } +} + +func TestRawSession_Close(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + sam, keys := setupTestSAM(t) + defer sam.Close() + + session, err := NewRawSession(sam, "test_close", keys, nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // Close the session + err = session.Close() + if err != nil { + t.Errorf("Close() error = %v", err) + } + + // Closing again should not error + err = session.Close() + if err != nil { + t.Errorf("Second Close() error = %v", err) + } +} + +func TestRawSession_Addr(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + sam, keys := setupTestSAM(t) + defer sam.Close() + + session, err := NewRawSession(sam, "test_addr", keys, nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + defer session.Close() + + addr := session.Addr() + expectedAddr := keys.Addr() + + if addr.Base32() != expectedAddr.Base32() { + t.Errorf("Addr() = %v, want %v", addr.Base32(), expectedAddr.Base32()) + } +} + +func TestRawSession_NewReader(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + sam, keys := setupTestSAM(t) + defer sam.Close() + + session, err := NewRawSession(sam, "test_reader", keys, nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + defer session.Close() + + reader := session.NewReader() + if reader == nil { + t.Error("NewReader() returned nil") + } + + if reader.session != session { + t.Error("Reader session reference is incorrect") + } + + // Verify channels are initialized + if reader.recvChan == nil { + t.Error("Reader recvChan is nil") + } + if reader.errorChan == nil { + t.Error("Reader errorChan is nil") + } + if reader.closeChan == nil { + t.Error("Reader closeChan is nil") + } + if reader.doneChan == nil { + t.Error("Reader doneChan is nil") + } +} + +func TestRawSession_NewWriter(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + sam, keys := setupTestSAM(t) + defer sam.Close() + + session, err := NewRawSession(sam, "test_writer", keys, nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + defer session.Close() + + writer := session.NewWriter() + if writer == nil { + t.Error("NewWriter() returned nil") + } + + if writer.session != session { + t.Error("Writer session reference is incorrect") + } + + if writer.timeout != 30 { + t.Errorf("Writer timeout = %v, want 30", writer.timeout) + } +} + +func TestRawSession_PacketConn(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + sam, keys := setupTestSAM(t) + defer sam.Close() + + session, err := NewRawSession(sam, "test_packetconn", keys, nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + defer session.Close() + + conn := session.PacketConn() + if conn == nil { + t.Error("PacketConn() returned nil") + } + + rawConn, ok := conn.(*RawConn) + if !ok { + t.Error("PacketConn() did not return a RawConn") + } + + if rawConn.session != session { + t.Error("RawConn session reference is incorrect") + } + + if rawConn.reader == nil { + t.Error("RawConn reader is nil") + } + + if rawConn.writer == nil { + t.Error("RawConn writer is nil") + } +} + +func TestRawAddr_Network(t *testing.T) { + addr := &RawAddr{} + if addr.Network() != "i2p-raw" { + t.Errorf("Network() = %v, want i2p-raw", addr.Network()) + } +} + +func TestRawAddr_String(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + sam, keys := setupTestSAM(t) + defer sam.Close() + + addr := &RawAddr{addr: keys.Addr()} + expected := keys.Addr().Base32() + + if addr.String() != expected { + t.Errorf("String() = %v, want %v", addr.String(), expected) + } +} diff --git a/raw/types_test.go b/raw/types_test.go new file mode 100644 index 00000000..f38d1583 --- /dev/null +++ b/raw/types_test.go @@ -0,0 +1,13 @@ +package raw + +import ( + "net" + + "github.com/go-i2p/go-sam-go/common" +) + +var ( + ds common.Session = &RawSession{} + dl net.Listener = &RawListener{} + dc net.PacketConn = &RawConn{} +)