github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/cli/test_history.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/evergreen-ci/evergreen" 9 "github.com/evergreen-ci/evergreen/model" 10 "github.com/evergreen-ci/evergreen/service" 11 "github.com/evergreen-ci/evergreen/util" 12 "github.com/pkg/errors" 13 ) 14 15 var () 16 17 const ( 18 prettyStringFormat = "%-25s %-15s %-40s%-40s %-40s %-40s \n" 19 timeFormat = "2006-01-02T15:04:05" 20 csvFormat = "csv" 21 prettyFormat = "pretty" 22 // jsonFormat = "json" // not used 23 ) 24 25 // TestHistoryCommand represents the test-history command in the CLI 26 type TestHistoryCommand struct { 27 GlobalOpts *Options `no-flag:"true"` 28 29 Project string `long:"project" short:"p" description:"project identifier, defaults to user's default project"` 30 Tasks []string `long:"task" description:"task name"` 31 Tests []string `long:"test" description:"test name"` 32 Variants []string `long:"variant" short:"v" description:"variant name"` 33 TaskStatuses []string `long:"task-status" description:"task status, either fail, pass, sysfail, or timeout "` 34 TestStatuses []string `long:"test-status" description:"test status, either fail, silentfail, pass, skip, or timeout "` 35 BeforeRevision string `long:"before-revision" description:"find tests that finished before a full revision hash (40 characters) (inclusive)"` 36 AfterRevision string `long:"after-revision" description:"find tests that finished after a full revision hash (40 characters) (exclusive)"` 37 // TODO EVG-1540 for user specific timezones. 38 BeforeDate string `long:"before-date" description:"find tests that finished before a date in format YYYY-MM-DDTHH:MM:SS in UTC"` 39 AfterDate string `long:"after-date" description:"find tests that finish after a date in format YYYY-MM-DDTHH:MM:SS in UTC"` 40 Earliest bool `long:"earliest" description:"sort test history from the earliest revisions to latest"` 41 Filepath string `long:"filepath" description:"path to directory where file is to be saved, only used with json or csv format"` 42 Format string `long:"format" description:"format to export test history, options are 'json', 'csv', 'pretty', default pretty to stdout"` 43 Limit int `long:"limit" description:"number of tasks to include the request. defaults to no limit, but you must specify either a limit or before/after revisions."` 44 } 45 46 // createUrlQuery returns a string url query parameter with relevant url parameters. 47 func createUrlQuery(testHistoryParameters model.TestHistoryParameters) string { 48 queryString := fmt.Sprintf("testStatuses=%v&taskStatuses=%v", strings.Join(testHistoryParameters.TestStatuses, ","), 49 strings.Join(testHistoryParameters.TaskStatuses, ",")) 50 51 if len(testHistoryParameters.TaskNames) > 0 { 52 queryString += fmt.Sprintf("&tasks=%v", strings.Join(testHistoryParameters.TaskNames, ",")) 53 } 54 55 if len(testHistoryParameters.TestNames) > 0 { 56 queryString += fmt.Sprintf("&tests=%v", strings.Join(testHistoryParameters.TestNames, ",")) 57 } 58 59 if len(testHistoryParameters.BuildVariants) > 0 { 60 queryString += fmt.Sprintf("&variants=%v", strings.Join(testHistoryParameters.BuildVariants, ",")) 61 } 62 63 if testHistoryParameters.BeforeRevision != "" { 64 queryString += fmt.Sprintf("&beforeRevision=%v", testHistoryParameters.BeforeRevision) 65 } 66 67 if testHistoryParameters.AfterRevision != "" { 68 queryString += fmt.Sprintf("&afterRevision=%v", testHistoryParameters.AfterRevision) 69 } 70 if !util.IsZeroTime(testHistoryParameters.BeforeDate) { 71 queryString += fmt.Sprintf("&beforeDate=%v", testHistoryParameters.BeforeDate.Format(time.RFC3339)) 72 } 73 if !util.IsZeroTime(testHistoryParameters.AfterDate) { 74 queryString += fmt.Sprintf("&afterDate=%v", testHistoryParameters.AfterDate.Format(time.RFC3339)) 75 } 76 77 if testHistoryParameters.Limit != 0 { 78 queryString += fmt.Sprintf("&limit=%v", testHistoryParameters.Limit) 79 } 80 81 return queryString 82 } 83 84 // Execute transfers the fields from a TestHistoryCommand to a TestHistoryParameter 85 // and validates them. It then gets the test history from the api endpoint 86 func (thc *TestHistoryCommand) Execute(_ []string) error { 87 _, rc, _, err := getAPIClients(thc.GlobalOpts) 88 if err != nil { 89 return err 90 } 91 92 // convert the test and tasks statuses to the correct evergreen statuses 93 testStatuses := []string{} 94 for _, s := range thc.TestStatuses { 95 switch s { 96 case "pass": 97 testStatuses = append(testStatuses, evergreen.TestSucceededStatus) 98 case "fail": 99 testStatuses = append(testStatuses, evergreen.TestFailedStatus) 100 case "silentfail": 101 testStatuses = append(testStatuses, evergreen.TestSilentlyFailedStatus) 102 case "skip": 103 testStatuses = append(testStatuses, evergreen.TestSkippedStatus) 104 case "timeout": 105 testStatuses = append(testStatuses, model.TaskTimeout) 106 } 107 } 108 109 taskStatuses := []string{} 110 for _, s := range thc.TaskStatuses { 111 switch s { 112 case "pass": 113 taskStatuses = append(taskStatuses, evergreen.TaskSucceeded) 114 case "fail": 115 taskStatuses = append(taskStatuses, evergreen.TaskFailed) 116 case "sysfail": 117 taskStatuses = append(taskStatuses, model.TaskSystemFailure) 118 case "timeout": 119 taskStatuses = append(taskStatuses, model.TaskTimeout) 120 } 121 } 122 123 sort := -1 124 if thc.Earliest { 125 sort = 1 126 } 127 128 if thc.AfterRevision != "" && len(thc.AfterRevision) != 40 { 129 return errors.Errorf("after revision must be a 40 character revision") 130 } 131 132 if thc.BeforeRevision != "" && len(thc.BeforeRevision) != 40 { 133 return errors.Errorf("before revision must be a 40 character revision") 134 } 135 136 if thc.Format == "" { 137 thc.Format = prettyFormat 138 } 139 beforeDate := time.Time{} 140 if thc.BeforeDate != "" { 141 beforeDate, err = time.Parse(timeFormat, thc.BeforeDate) 142 if err != nil { 143 return errors.Errorf("before date should have format YYYY-MM-DDTHH:MM:SS, error: %v", err) 144 } 145 } 146 afterDate := time.Time{} 147 if thc.AfterDate != "" { 148 afterDate, err = time.Parse(timeFormat, thc.AfterDate) 149 if err != nil { 150 return errors.Errorf("after date should have format YYYY-MM-DDTHH:MM:SS, error: %v", err) 151 } 152 } 153 154 // create a test history parameter struct and validate it 155 testHistoryParameters := model.TestHistoryParameters{ 156 Project: thc.Project, 157 TaskNames: thc.Tasks, 158 TestNames: thc.Tests, 159 BuildVariants: thc.Variants, 160 TaskStatuses: taskStatuses, 161 TestStatuses: testStatuses, 162 BeforeRevision: thc.BeforeRevision, 163 AfterRevision: thc.AfterRevision, 164 BeforeDate: beforeDate, 165 AfterDate: afterDate, 166 Sort: sort, 167 Limit: thc.Limit, 168 } 169 170 if err := testHistoryParameters.SetDefaultsAndValidate(); err != nil { 171 return err 172 } 173 isCSV := false 174 if thc.Format == csvFormat { 175 isCSV = true 176 } 177 body, err := rc.GetTestHistory(testHistoryParameters.Project, createUrlQuery(testHistoryParameters), isCSV) 178 if err != nil { 179 return err 180 } 181 defer body.Close() 182 183 if thc.Format == prettyFormat { 184 results := []service.RestTestHistoryResult{} 185 186 if err := util.ReadJSONInto(body, &results); err != nil { 187 return err 188 } 189 190 fmt.Printf(prettyStringFormat, "Start Time", "Duration(ms)", "Variant", "Task Name", "Test File", "URL") 191 for _, thr := range results { 192 if !util.IsZeroTime(thr.StartTime) { 193 formattedStart := thr.StartTime.Format(time.ANSIC) 194 fmt.Printf(prettyStringFormat, formattedStart, thr.DurationMS, thr.BuildVariant, 195 thr.TaskName, thr.TestFile, thr.Url) 196 } 197 } 198 return nil 199 } 200 201 return WriteToFile(body, thc.Filepath) 202 203 }