// Test Suite package jenkins import ( "encoding/json" "net/http" "net/http/httptest" "sort" "strings" "testing" "time" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/testutil" ) func TestJobRequest(t *testing.T) { tests := []struct { input jobRequest hierarchyName string URL string }{ { jobRequest{}, "", "", }, { jobRequest{ name: "1", parents: []string{"3", "2"}, }, "3/2/1", "/job/3/job/2/job/1/api/json", }, { jobRequest{ name: "job 3", parents: []string{"job 1", "job 2"}, }, "job 1/job 2/job 3", "/job/job%201/job/job%202/job/job%203/api/json", }, } for _, test := range tests { hierarchyName := test.input.hierarchyName() URL := test.input.URL() if hierarchyName != test.hierarchyName { t.Errorf("Expected %s, got %s\n", test.hierarchyName, hierarchyName) } if test.URL != "" && URL != test.URL { t.Errorf("Expected %s, got %s\n", test.URL, URL) } } } func TestResultCode(t *testing.T) { tests := []struct { input string output int }{ {"SUCCESS", 0}, {"Failure", 1}, {"NOT_BUILT", 2}, {"UNSTABLE", 3}, {"ABORTED", 4}, } for _, test := range tests { output := mapResultCode(test.input) if output != test.output { t.Errorf("Expected %d, got %d\n", test.output, output) } } } type mockHandler struct { // responseMap is the path to response interface // we will output the serialized response in json when serving http // example '/computer/api/json': *gojenkins. responseMap map[string]interface{} } func (h mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { o, ok := h.responseMap[r.URL.RequestURI()] if !ok { w.WriteHeader(http.StatusNotFound) return } b, err := json.Marshal(o) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } if len(b) == 0 { w.WriteHeader(http.StatusNoContent) return } w.Write(b) } func TestGatherNodeData(t *testing.T) { tests := []struct { name string input mockHandler output *testutil.Accumulator wantErr bool }{ { name: "bad node data", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": struct{}{}, "/computer/api/json": nodeResponse{ Computers: []node{ {}, {}, {}, }, }, }, }, wantErr: true, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{ { Tags: map[string]string{ "source": "127.0.0.1", }, Fields: map[string]interface{}{ "busy_executors": 0, "total_executors": 0, }, }, }, }, }, { name: "empty monitor data", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": struct{}{}, "/computer/api/json": nodeResponse{ Computers: []node{ {DisplayName: "master"}, {DisplayName: "node1"}, }, }, }, }, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{}, }, }, { name: "filtered nodes", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": struct{}{}, "/computer/api/json": nodeResponse{ BusyExecutors: 4, TotalExecutors: 8, Computers: []node{ {DisplayName: "ignore-1"}, {DisplayName: "ignore-2"}, }, }, }, }, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{ { Tags: map[string]string{ "source": "127.0.0.1", }, Fields: map[string]interface{}{ "busy_executors": 4, "total_executors": 8, }, }, }, }, }, { name: "normal data collection", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": struct{}{}, "/computer/api/json": nodeResponse{ BusyExecutors: 4, TotalExecutors: 8, Computers: []node{ { DisplayName: "master", MonitorData: monitorData{ HudsonNodeMonitorsArchitectureMonitor: "linux", HudsonNodeMonitorsResponseTimeMonitor: &responseTimeMonitor{ Average: 10032, }, HudsonNodeMonitorsDiskSpaceMonitor: &nodeSpaceMonitor{ Path: "/path/1", Size: 123, }, HudsonNodeMonitorsTemporarySpaceMonitor: &nodeSpaceMonitor{ Path: "/path/2", Size: 245, }, HudsonNodeMonitorsSwapSpaceMonitor: &swapSpaceMonitor{ SwapAvailable: 212, SwapTotal: 500, MemoryAvailable: 101, MemoryTotal: 500, }, }, Offline: false, }, }, }, }, }, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{ { Tags: map[string]string{ "source": "127.0.0.1", }, Fields: map[string]interface{}{ "busy_executors": 4, "total_executors": 8, }, }, { Tags: map[string]string{ "node_name": "master", "arch": "linux", "status": "online", "disk_path": "/path/1", "temp_path": "/path/2", "source": "127.0.0.1", }, Fields: map[string]interface{}{ "response_time": int64(10032), "disk_available": float64(123), "temp_available": float64(245), "swap_available": float64(212), "swap_total": float64(500), "memory_available": float64(101), "memory_total": float64(500), }, }, }, }, }, { name: "slave is offline", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": struct{}{}, "/computer/api/json": nodeResponse{ BusyExecutors: 4, TotalExecutors: 8, Computers: []node{ { DisplayName: "slave", MonitorData: monitorData{}, NumExecutors: 1, Offline: true, }, }, }, }, }, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{ { Tags: map[string]string{ "source": "127.0.0.1", }, Fields: map[string]interface{}{ "busy_executors": 4, "total_executors": 8, }, }, { Tags: map[string]string{ "node_name": "slave", "status": "offline", }, Fields: map[string]interface{}{ "num_executors": 1, }, }, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ts := httptest.NewServer(test.input) defer ts.Close() j := &Jenkins{ Log: testutil.Logger{}, URL: ts.URL, ResponseTimeout: internal.Duration{Duration: time.Microsecond}, NodeExclude: []string{"ignore-1", "ignore-2"}, } te := j.initialize(&http.Client{Transport: &http.Transport{}}) acc := new(testutil.Accumulator) j.gatherNodesData(acc) if err := acc.FirstError(); err != nil { te = err } if !test.wantErr && te != nil { t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error()) } else if test.wantErr && te == nil { t.Fatalf("%s: expected err, got nil", test.name) } if test.output == nil && len(acc.Metrics) > 0 { t.Fatalf("%s: collected extra data %s", test.name, acc.Metrics) } else if test.output != nil && len(test.output.Metrics) > 0 { for i := 0; i < len(test.output.Metrics); i++ { for k, m := range test.output.Metrics[i].Tags { if acc.Metrics[i].Tags[k] != m { t.Fatalf("%s: tag %s metrics unmatch Expected %s, got %s\n", test.name, k, m, acc.Metrics[0].Tags[k]) } } for k, m := range test.output.Metrics[i].Fields { if acc.Metrics[i].Fields[k] != m { t.Fatalf("%s: field %s metrics unmatch Expected %v(%T), got %v(%T)\n", test.name, k, m, m, acc.Metrics[0].Fields[k], acc.Metrics[0].Fields[k]) } } } } }) } } func TestInitialize(t *testing.T) { mh := mockHandler{ responseMap: map[string]interface{}{ "/api/json": struct{}{}, }, } ts := httptest.NewServer(mh) defer ts.Close() mockClient := &http.Client{Transport: &http.Transport{}} tests := []struct { // name of the test name string input *Jenkins output *Jenkins wantErr bool }{ { name: "bad jenkins config", input: &Jenkins{ Log: testutil.Logger{}, URL: "http://a bad url", ResponseTimeout: internal.Duration{Duration: time.Microsecond}, }, wantErr: true, }, { name: "has filter", input: &Jenkins{ Log: testutil.Logger{}, URL: ts.URL, ResponseTimeout: internal.Duration{Duration: time.Microsecond}, JobExclude: []string{"job1", "job2"}, NodeExclude: []string{"node1", "node2"}, }, }, { name: "default config", input: &Jenkins{ Log: testutil.Logger{}, URL: ts.URL, ResponseTimeout: internal.Duration{Duration: time.Microsecond}, }, output: &Jenkins{ Log: testutil.Logger{}, MaxConnections: 5, MaxSubJobPerLayer: 10, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { te := test.input.initialize(mockClient) if !test.wantErr && te != nil { t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error()) } else if test.wantErr && te == nil { t.Fatalf("%s: expected err, got nil", test.name) } if test.output != nil { if test.input.client == nil { t.Fatalf("%s: failed %s, jenkins instance shouldn't be nil", test.name, te.Error()) } if test.input.MaxConnections != test.output.MaxConnections { t.Fatalf("%s: different MaxConnections Expected %d, got %d\n", test.name, test.output.MaxConnections, test.input.MaxConnections) } } }) } } func TestGatherJobs(t *testing.T) { tests := []struct { name string input mockHandler output *testutil.Accumulator wantErr bool }{ { name: "empty job", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{}, }, }, }, { name: "bad inner jobs", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "job1"}, }, }, }, }, wantErr: true, }, { name: "jobs has no build", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "job1"}, }, }, "/job/job1/api/json": &jobResponse{}, }, }, }, { name: "bad build info", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "job1"}, }, }, "/job/job1/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 1, }, }, }, }, wantErr: true, }, { name: "ignore building job", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "job1"}, }, }, "/job/job1/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 1, }, }, "/job/job1/1/api/json": &buildResponse{ Building: true, }, }, }, }, { name: "ignore old build", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "job1"}, }, }, "/job/job1/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 2, }, }, "/job/job1/2/api/json": &buildResponse{ Building: false, Timestamp: 100, }, }, }, }, { name: "gather metrics", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "job1"}, {Name: "job2"}, }, }, "/job/job1/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 3, }, }, "/job/job2/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 1, }, }, "/job/job1/3/api/json": &buildResponse{ Building: false, Result: "SUCCESS", Duration: 25558, Timestamp: (time.Now().Unix() - int64(time.Minute.Seconds())) * 1000, }, "/job/job2/1/api/json": &buildResponse{ Building: false, Result: "FAILURE", Duration: 1558, Timestamp: (time.Now().Unix() - int64(time.Minute.Seconds())) * 1000, }, }, }, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{ { Tags: map[string]string{ "name": "job1", "result": "SUCCESS", }, Fields: map[string]interface{}{ "duration": int64(25558), "result_code": 0, }, }, { Tags: map[string]string{ "name": "job2", "result": "FAILURE", }, Fields: map[string]interface{}{ "duration": int64(1558), "result_code": 1, }, }, }, }, }, { name: "gather metrics for jobs with space", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "job 1"}, }, }, "/job/job%201/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 3, }, }, "/job/job%201/3/api/json": &buildResponse{ Building: false, Result: "SUCCESS", Duration: 25558, Timestamp: (time.Now().Unix() - int64(time.Minute.Seconds())) * 1000, }, }, }, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{ { Tags: map[string]string{ "name": "job 1", "result": "SUCCESS", }, Fields: map[string]interface{}{ "duration": int64(25558), "result_code": 0, }, }, }, }, }, { name: "gather sub jobs, jobs filter", input: mockHandler{ responseMap: map[string]interface{}{ "/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "apps"}, {Name: "ignore-1"}, }, }, "/job/apps/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "k8s-cloud"}, {Name: "chronograf"}, {Name: "ignore-all"}, }, }, "/job/apps/job/ignore-all/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "1"}, {Name: "2"}, }, }, "/job/apps/job/chronograf/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 1, }, }, "/job/apps/job/k8s-cloud/api/json": &jobResponse{ Jobs: []innerJob{ {Name: "PR-100"}, {Name: "PR-101"}, {Name: "PR-ignore2"}, {Name: "PR 1"}, {Name: "PR ignore"}, }, }, "/job/apps/job/k8s-cloud/job/PR-100/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 1, }, }, "/job/apps/job/k8s-cloud/job/PR-101/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 4, }, }, "/job/apps/job/k8s-cloud/job/PR%201/api/json": &jobResponse{ LastBuild: jobBuild{ Number: 1, }, }, "/job/apps/job/chronograf/1/api/json": &buildResponse{ Building: false, Result: "FAILURE", Duration: 1558, Timestamp: (time.Now().Unix() - int64(time.Minute.Seconds())) * 1000, }, "/job/apps/job/k8s-cloud/job/PR-101/4/api/json": &buildResponse{ Building: false, Result: "SUCCESS", Duration: 76558, Timestamp: (time.Now().Unix() - int64(time.Minute.Seconds())) * 1000, }, "/job/apps/job/k8s-cloud/job/PR-100/1/api/json": &buildResponse{ Building: false, Result: "SUCCESS", Duration: 91558, Timestamp: (time.Now().Unix() - int64(time.Minute.Seconds())) * 1000, }, "/job/apps/job/k8s-cloud/job/PR%201/1/api/json": &buildResponse{ Building: false, Result: "SUCCESS", Duration: 87832, Timestamp: (time.Now().Unix() - int64(time.Minute.Seconds())) * 1000, }, }, }, output: &testutil.Accumulator{ Metrics: []*testutil.Metric{ { Tags: map[string]string{ "name": "PR 1", "parents": "apps/k8s-cloud", "result": "SUCCESS", }, Fields: map[string]interface{}{ "duration": int64(87832), "result_code": 0, }, }, { Tags: map[string]string{ "name": "PR-100", "parents": "apps/k8s-cloud", "result": "SUCCESS", }, Fields: map[string]interface{}{ "duration": int64(91558), "result_code": 0, }, }, { Tags: map[string]string{ "name": "PR-101", "parents": "apps/k8s-cloud", "result": "SUCCESS", }, Fields: map[string]interface{}{ "duration": int64(76558), "result_code": 0, }, }, { Tags: map[string]string{ "name": "chronograf", "parents": "apps", "result": "FAILURE", }, Fields: map[string]interface{}{ "duration": int64(1558), "result_code": 1, }, }, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ts := httptest.NewServer(test.input) defer ts.Close() j := &Jenkins{ Log: testutil.Logger{}, URL: ts.URL, MaxBuildAge: internal.Duration{Duration: time.Hour}, ResponseTimeout: internal.Duration{Duration: time.Microsecond}, JobExclude: []string{ "ignore-1", "apps/ignore-all/*", "apps/k8s-cloud/PR-ignore2", "apps/k8s-cloud/PR ignore", }, } te := j.initialize(&http.Client{Transport: &http.Transport{}}) acc := new(testutil.Accumulator) j.gatherJobs(acc) if err := acc.FirstError(); err != nil { te = err } if !test.wantErr && te != nil { t.Fatalf("%s: failed %s, expected to be nil", test.name, te.Error()) } else if test.wantErr && te == nil { t.Fatalf("%s: expected err, got nil", test.name) } if test.output != nil && len(test.output.Metrics) > 0 { // sort metrics sort.Slice(acc.Metrics, func(i, j int) bool { return strings.Compare(acc.Metrics[i].Tags["name"], acc.Metrics[j].Tags["name"]) < 0 }) for i := range test.output.Metrics { for k, m := range test.output.Metrics[i].Tags { if acc.Metrics[i].Tags[k] != m { t.Fatalf("%s: tag %s metrics unmatch Expected %s, got %s\n", test.name, k, m, acc.Metrics[i].Tags[k]) } } for k, m := range test.output.Metrics[i].Fields { if acc.Metrics[i].Fields[k] != m { t.Fatalf("%s: field %s metrics unmatch Expected %v(%T), got %v(%T)\n", test.name, k, m, m, acc.Metrics[i].Fields[k], acc.Metrics[0].Fields[k]) } } } } }) } }