package client import ( "crypto/md5" "encoding/hex" "errors" "flag" "fmt" "os" "testing" "time" ) const ( TestStr = "Hello world" ) var ( client *Client runIntegrationTests bool ) func TestMain(m *testing.M) { integrationsTestFlag := flag.Bool("integration", false, "Run the integration tests (in addition to the unit tests)") flag.Parse() if integrationsTestFlag != nil { runIntegrationTests = *integrationsTestFlag } code := m.Run() os.Exit(code) } func TestClientAddServer(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } t.Log("Add local server 127.0.0.1:4730") var err error if client, err = New(Network, "127.0.0.1:4730"); err != nil { t.Fatal(err) } client.ErrorHandler = func(e error) { t.Log(e) } } func TestClientEcho(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } echo, err := client.Echo([]byte(TestStr)) if err != nil { t.Error(err) return } if string(echo) != TestStr { t.Errorf("Echo error, %s expected, %s got", TestStr, echo) return } } func TestClientDoBg(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } handle, err := client.DoBg("ToUpper", []byte("abcdef"), JobLow) if err != nil { t.Error(err) return } if handle == "" { t.Error("Handle is empty.") } else { t.Log(handle) } } func TestClientDoBgWithId(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } data := []byte("abcdef") hash := md5.Sum(data) id := hex.EncodeToString(hash[:]) handle, err := client.DoBgWithId("ToUpper", data, JobLow, id) if err != nil { t.Error(err) return } if handle == "" { t.Error("Handle is empty.") } else { t.Log(handle) } } func TestClientDoBgWithIdFailsIfNoId(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } data := []byte("abcdef") id := "" _, err := client.DoBgWithId("ToUpper", data, JobLow, id) if err == nil { t.Error("Expecting error") return } if err.Error() != "Invalid ID" { t.Error(fmt.Sprintf("Expecting \"Invalid ID\" error, got %s.", err.Error())) return } } func TestClientDo(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } jobHandler := func(job *Response) { str := string(job.Data) if str == "ABCDEF" { t.Log(str) } else { t.Errorf("Invalid data: %s", job.Data) } return } handle, err := client.Do("ToUpper", []byte("abcdef"), JobLow, jobHandler) if err != nil { t.Error(err) return } if handle == "" { t.Error("Handle is empty.") } else { t.Log(handle) } } func TestClientDoWithId(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } jobHandler := func(job *Response) { str := string(job.Data) if str == "ABCDEF" { t.Log(str) } else { t.Errorf("Invalid data: %s", job.Data) } return } data := []byte("abcdef") hash := md5.Sum(data) id := hex.EncodeToString(hash[:]) handle, err := client.DoWithId("ToUpper", data, JobLow, jobHandler, id) if err != nil { t.Error(err) return } if handle == "" { t.Error("Handle is empty.") } else { t.Log(handle) } } func TestClientDoWithIdFailsIfNoId(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } jobHandler := func(job *Response) { str := string(job.Data) if str == "ABCDEF" { t.Log(str) } else { t.Errorf("Invalid data: %s", job.Data) } return } data := []byte("abcdef") id := "" _, err := client.DoWithId("ToUpper", data, JobLow, jobHandler, id) if err == nil { t.Error("Expecting error") return } if err.Error() != "Invalid ID" { t.Error(fmt.Sprintf("Expecting \"Invalid ID\" error, got %s.", err.Error())) return } } func TestClientDoWithIdCheckSameHandle(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } jobHandler := func(job *Response) { return } data := []byte("{productId:123,categoryId:1}") id := "123" handle1, err := client.DoWithId("PublishProduct", data, JobLow, jobHandler, id) if err != nil { t.Error(err) return } if handle1 == "" { t.Error("Handle is empty.") } else { t.Log(handle1) } handle2, err := client.DoWithId("PublishProduct", data, JobLow, jobHandler, id) if err != nil { t.Error(err) return } if handle2 == "" { t.Error("Handle is empty.") } else { t.Log(handle2) } if handle1 != handle2 { t.Error("expecting the same handle when using the same id on the same Job name") } } func TestClientDoWithIdCheckDifferentHandleOnDifferentJobs(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } jobHandler := func(job *Response) { return } data := []byte("{productId:123}") id := "123" handle1, err := client.DoWithId("PublishProduct", data, JobLow, jobHandler, id) if err != nil { t.Error(err) return } if handle1 == "" { t.Error("Handle is empty.") } else { t.Log(handle1) } handle2, err := client.DoWithId("DeleteProduct", data, JobLow, jobHandler, id) if err != nil { t.Error(err) return } if handle2 == "" { t.Error("Handle is empty.") } else { t.Log(handle2) } if handle1 == handle2 { t.Error("expecting different handles because there are different job names") } } func TestClientMultiDo(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } // This integration test requires that examples/pl/worker_multi.pl be running. // // Test invocation is: // go test -integration -timeout 10s -run '^TestClient(AddServer|MultiDo)$' // // Send 1000 requests to go through all race conditions const nreqs = 1000 errCh := make(chan error) gotCh := make(chan string, nreqs) olderrh := client.ErrorHandler client.ErrorHandler = func(e error) { errCh <- e } client.ResponseTimeout = 5 * time.Second defer func() { client.ErrorHandler = olderrh }() nextJobCh := make(chan struct{}) defer close(nextJobCh) go func() { for range nextJobCh { start := time.Now() handle, err := client.Do("PerlToUpper", []byte("abcdef"), JobNormal, func(r *Response) { gotCh <- string(r.Data) }) if err == ErrLostConn && time.Since(start) > client.ResponseTimeout { errCh <- errors.New("Impossible 'lost conn', deadlock bug detected") } else if err != nil { errCh <- err } if handle == "" { errCh <- errors.New("Handle is empty.") } } }() for i := 0; i < nreqs; i++ { select { case err := <-errCh: t.Fatal(err) case nextJobCh <- struct{}{}: } } remaining := nreqs for remaining > 0 { select { case err := <-errCh: t.Fatal(err) case got := <-gotCh: if got != "ABCDEF" { t.Error("Unexpected response from PerlDoUpper: ", got) } remaining-- t.Logf("%d response remaining", remaining) } } } func TestClientStatus(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } status, err := client.Status("handle not exists") if err != nil { t.Error(err) return } if status.Known { t.Errorf("The job (%s) shouldn't be known.", status.Handle) return } if status.Running { t.Errorf("The job (%s) shouldn't be running.", status.Handle) return } handle, err := client.Do("Delay5sec", []byte("abcdef"), JobLow, nil) if err != nil { t.Error(err) return } status, err = client.Status(handle) if err != nil { t.Error(err) return } if !status.Known { t.Errorf("The job (%s) should be known.", status.Handle) return } if status.Running { t.Errorf("The job (%s) shouldn't be running.", status.Handle) return } } func TestClientClose(t *testing.T) { if !runIntegrationTests { t.Skip("To run this test, use: go test -integration") } if err := client.Close(); err != nil { t.Error(err) } }