Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Testing Guide for gocql

This document explains how to run tests for the gocql project.

## Quick Start

### Run All Unit Tests

The easiest way to run all unit tests:

```bash
./run_unit_tests.sh
```

This script:
- Builds the test binary with the `unit` tag
- Runs all unit tests
- Reports pass/fail status

### Manual Test Execution

If you prefer to run tests manually:

```bash
# Build the test binary
go test -tags unit -c -o /tmp/gocql_unit_test

# Run the tests
/tmp/gocql_unit_test
```

## Test Types

### Unit Tests

Unit tests are tagged with `// +build all unit` and test individual components in isolation.

**Run unit tests:**
```bash
./run_unit_tests.sh
```

**What they test:**
- Marshaling/unmarshaling
- Connection logic
- Configuration propagation
- Individual component behavior

**Requirements:**
- No Cassandra instance needed
- No network connectivity required
- Fast execution

### Integration Tests

Integration tests require a running Cassandra cluster and are tagged with `// +build integration`.

**Run integration tests:**
```bash
# Requires ccm (Cassandra Cluster Manager) and a running cluster
./integration.sh <cassandra-version> <auth-enabled>

# Example:
./integration.sh 3.0.14 false
```

**What they test:**
- Full query execution
- Cluster connectivity
- Authentication
- SSL/TLS
- Multi-node scenarios

**Requirements:**
- Cassandra cluster running (via ccm)
- Network connectivity
- Longer execution time
3 changes: 2 additions & 1 deletion cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type ClusterConfig struct {
ProtoVersion int
Timeout time.Duration // connection timeout (default: 600ms)
ConnectTimeout time.Duration // initial connection timeout, used during initial dial to server (default: 600ms)
ConnMaxLifetime time.Duration // SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
Port int // port (default: 9042)
Keyspace string // initial keyspace (optional)
NumConns int // number of connections per host (default: 2)
Expand Down Expand Up @@ -129,7 +130,7 @@ type ClusterConfig struct {
// created from this session.
ConnectObserver ConnectObserver

// FrameHeaderObserver will set the provided frame header observer on all frames' headers created from this session.
// FrameHeaderObserver will set the provided frame header observer on all frames' headers created from this session.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was done by gofmt

// Use it to collect metrics / stats from frames by providing an implementation of FrameHeaderObserver.
FrameHeaderObserver FrameHeaderObserver

Expand Down
32 changes: 22 additions & 10 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@ type SslOptions struct {
}

type ConnConfig struct {
ProtoVersion int
CQLVersion string
Timeout time.Duration
ConnectTimeout time.Duration
Compressor Compressor
Authenticator Authenticator
Keepalive time.Duration
tlsConfig *tls.Config
ProtoVersion int
CQLVersion string
Timeout time.Duration
ConnectTimeout time.Duration
ConnMaxLifetime time.Duration
Compressor Compressor
Authenticator Authenticator
Keepalive time.Duration
tlsConfig *tls.Config
}

type ConnErrorHandler interface {
Expand Down Expand Up @@ -147,8 +148,9 @@ type Conn struct {

session *Session

closed int32
quit chan struct{}
closed int32
quit chan struct{}
createdAt time.Time

timeouts int64
}
Expand Down Expand Up @@ -205,6 +207,7 @@ func (s *Session) dial(host *HostInfo, cfg *ConnConfig, errorHandler ConnErrorHa
streams: streams.New(cfg.ProtoVersion),
host: host,
frameObserver: s.frameObserver,
createdAt: time.Now(),
}

if cfg.Keepalive > 0 {
Expand Down Expand Up @@ -423,6 +426,15 @@ func (c *Conn) Close() {
c.closeWithError(nil)
}

// IsExpired returns true if the connection has exceeded its maximum lifetime.
// Returns false if ConnMaxLifetime is not set (zero value).
func (c *Conn) IsExpired() bool {
if c.cfg.ConnMaxLifetime <= 0 {
return false
}
return time.Since(c.createdAt) > c.cfg.ConnMaxLifetime
}

// Serve starts the stream multiplexer for this connection, which is required
// to execute any queries. This method runs as long as the connection is
// open and is therefore usually called in a separate goroutine.
Expand Down
87 changes: 87 additions & 0 deletions conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -960,3 +960,90 @@ func (srv *TestServer) readFrame(conn net.Conn) (*framer, error) {

return framer, nil
}

func TestConnMaxLifetime(t *testing.T) {
// Test that ConnMaxLifetime is properly set in ConnConfig
cluster := NewCluster("127.0.0.1")
cluster.ConnMaxLifetime = 5 * time.Minute

connCfg, err := connConfig(cluster)
if err != nil {
t.Fatalf("Failed to create ConnConfig: %v", err)
}

if connCfg.ConnMaxLifetime != 5*time.Minute {
t.Errorf("Expected ConnMaxLifetime to be 5m, got %v", connCfg.ConnMaxLifetime)
}
}

func TestConnIsExpired(t *testing.T) {
tests := []struct {
name string
connMaxLifetime time.Duration
connAge time.Duration
expectedExpired bool
}{
{
name: "No max lifetime set",
connMaxLifetime: 0,
connAge: 10 * time.Minute,
expectedExpired: false,
},
{
name: "Connection not expired",
connMaxLifetime: 10 * time.Minute,
connAge: 5 * time.Minute,
expectedExpired: false,
},
{
name: "Connection expired",
connMaxLifetime: 5 * time.Minute,
connAge: 10 * time.Minute,
expectedExpired: true,
},
{
name: "Connection just under lifetime",
connMaxLifetime: 10 * time.Minute,
connAge: 9*time.Minute + 59*time.Second,
expectedExpired: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conn := &Conn{
cfg: &ConnConfig{
ConnMaxLifetime: tt.connMaxLifetime,
},
createdAt: time.Now().Add(-tt.connAge),
}

if got := conn.IsExpired(); got != tt.expectedExpired {
t.Errorf("IsExpired() = %v, want %v", got, tt.expectedExpired)
}
})
}
}

func TestConnIsExpiredJustExpired(t *testing.T) {
// Test a connection that expires just after the max lifetime
conn := &Conn{
cfg: &ConnConfig{
ConnMaxLifetime: 100 * time.Millisecond,
},
createdAt: time.Now(),
}

// Connection should not be expired immediately
if conn.IsExpired() {
t.Error("Connection should not be expired immediately after creation")
}

// Wait for the connection to expire
time.Sleep(150 * time.Millisecond)

// Connection should now be expired
if !conn.IsExpired() {
t.Error("Connection should be expired after max lifetime")
}
}
Loading