diff --git a/client_test.go b/client_test.go index d687bc2..e7172d0 100644 --- a/client_test.go +++ b/client_test.go @@ -5,6 +5,9 @@ package girc import ( + "context" + "io" + "strings" "testing" "time" ) @@ -35,6 +38,48 @@ func TestDisableTracking(t *testing.T) { } } +func TestConfigValid(t *testing.T) { + conf := Config{ + Server: "irc.example.com", Port: 6667, + Nick: "test", User: "test", Name: "Realname", + } + + var err error + if err = conf.isValid(); err != nil { + t.Fatalf("valid config failed Config.isValid() with: %s", err) + } + + conf.Server = "" + if err = conf.isValid(); err == nil { + t.Fatalf("invalid server passed validation check: %s", err) + } + conf.Server = "irc.example.com" + + conf.Port = 100000 + if err = conf.isValid(); err == nil { + t.Fatalf("invalid port passed validation check: %s", err) + } + conf.Port = 0 // Assumes "default". + if err = conf.isValid(); err != nil { + t.Fatalf("valid default failed validation check: %s", err) + } + if conf.Port != 6667 { + t.Fatal("irc port was not defaulted to 6667") + } + + conf.Nick = "invalid nick" + if err = conf.isValid(); err == nil { + t.Fatalf("invalid nick passed validation check: %s", err) + } + conf.User = "test" + + conf.User = "invalid user" + if err = conf.isValid(); err == nil { + t.Fatalf("invalid user passed validation check: %s", err) + } + conf.User = "test" +} + func TestClientLifetime(t *testing.T) { client := New(Config{ Server: "dummy.int", @@ -44,24 +89,31 @@ func TestClientLifetime(t *testing.T) { Name: "Testing123", }) - time.Sleep(500 * time.Millisecond) tm := client.Lifetime() - if tm < 400*time.Millisecond || tm > 10*time.Second { + if tm < 0 || tm > 2*time.Second { t.Fatalf("Client.Lifetime() = %q, out of bounds", tm) } } func TestClientUptime(t *testing.T) { c, conn, server := genMockConn() - defer conn.Close() defer server.Close() + ctx, cancel := context.WithCancel(context.Background()) + c.Handlers.Add(INITIALIZED, func(c *Client, e Event) { + cancel() + }) + go c.MockConnect(server) defer c.Close() - time.Sleep(500 * time.Millisecond) + select { + case <-ctx.Done(): + case <-time.After(2 * time.Second): + t.Fatal("Client.Uptime() timed out") + } uptime, err := c.Uptime() if err != nil { @@ -74,14 +126,14 @@ func TestClientUptime(t *testing.T) { t.Fatalf("Client.ConnSince() = %s, wanted time", err) } - if since < 400*time.Millisecond || since > 10*time.Second || *connsince < 400*time.Millisecond || *connsince > 10*time.Second { + if since < 0 || since > 4*time.Second || *connsince < 0 || *connsince > 4*time.Second { t.Fatalf("Client.Uptime() = %q (%q, connsince: %q), out of bounds", uptime, since, connsince) } // Verify the time we got from Client.Uptime() and Client.ConnSince() are // within reach of eachother. - if *connsince-since > 1*time.Second { + if *connsince-since > 2*time.Second { t.Fatalf("Client.Uptime() (diff) = %q, Client.ConnSince() = %q, differ too much", since, connsince) } @@ -92,14 +144,22 @@ func TestClientUptime(t *testing.T) { func TestClientGet(t *testing.T) { c, conn, server := genMockConn() - defer conn.Close() defer server.Close() + ctx, cancel := context.WithCancel(context.Background()) + c.Handlers.Add(INITIALIZED, func(c *Client, e Event) { + cancel() + }) + go c.MockConnect(server) defer c.Close() - time.Sleep(1 * time.Second) + select { + case <-ctx.Done(): + case <-time.After(2 * time.Second): + t.Fatal("timed out during connect") + } if nick := c.GetNick(); nick != c.Config.Nick { t.Fatalf("Client.GetNick() = %q though should be %q", nick, c.Config.Nick) @@ -108,4 +168,56 @@ func TestClientGet(t *testing.T) { if user := c.GetIdent(); user != c.Config.User { t.Fatalf("Client.GetIdent() = %q though should be %q", user, c.Config.User) } + + if !strings.Contains(c.String(), "connected:true") { + t.Fatalf("Client.String() == %q, doesn't contain 'connected:true'", c.String()) + } +} + +func TestClientClose(t *testing.T) { + c, conn, server := genMockConn() + defer conn.Close() + defer server.Close() + + errchan := make(chan error, 1) + ctx, cancel := context.WithCancel(context.Background()) + + c.Handlers.AddBg(STOPPED, func(c *Client, e Event) { + cancel() + }) + c.Handlers.AddBg(INITIALIZED, func(c *Client, e Event) { + c.Close() + }) + + go func() { + errchan <- c.MockConnect(server) + }() + defer c.Close() + + select { + case <-time.After(5 * time.Second): + t.Fatal("Client.Close() timed out") + cancel() + case <-ctx.Done(): + } + + select { + case err := <-errchan: + if err != nil && err != io.ErrClosedPipe { + t.Fatalf("connect returned with error when close was invoked: %s", err) + } + case <-time.After(5 * time.Second): + t.Fatal("timed out while waiting for connect to return") + } + + close(errchan) +} + +func TestClientExec(t *testing.T) { + c, conn, server := genMockConn() + defer conn.Close() + defer server.Close() + + go c.MockConnect(server) + defer c.Close() } diff --git a/conn.go b/conn.go index 5c26f20..80f22ed 100644 --- a/conn.go +++ b/conn.go @@ -328,12 +328,12 @@ func (c *Client) internalConnect(mock net.Conn) error { } // Make sure that the connection is closed if not already. - c.mu.Lock() + c.mu.RLock() c.conn.mu.Lock() c.conn.connected = false _ = c.conn.Close() c.conn.mu.Unlock() - c.mu.Unlock() + c.mu.RUnlock() // Once we have our error/result, let all other functions know we're done. c.debug.Print("waiting for all routines to finish")