github.com/kubeshop/testkube@v1.17.23/contrib/executor/gradle/pkg/runner/runner.go (about) 1 package runner 2 3 import ( 4 "context" 5 "fmt" 6 "io/fs" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/joshdk/go-junit" 12 "github.com/pkg/errors" 13 14 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 15 "github.com/kubeshop/testkube/pkg/envs" 16 "github.com/kubeshop/testkube/pkg/executor" 17 "github.com/kubeshop/testkube/pkg/executor/agent" 18 "github.com/kubeshop/testkube/pkg/executor/env" 19 "github.com/kubeshop/testkube/pkg/executor/output" 20 "github.com/kubeshop/testkube/pkg/executor/runner" 21 "github.com/kubeshop/testkube/pkg/executor/scraper" 22 "github.com/kubeshop/testkube/pkg/executor/scraper/factory" 23 "github.com/kubeshop/testkube/pkg/ui" 24 ) 25 26 func NewRunner(ctx context.Context, params envs.Params) (*GradleRunner, error) { 27 output.PrintLogf("%s Preparing test runner", ui.IconTruck) 28 29 var err error 30 r := &GradleRunner{ 31 params: params, 32 } 33 34 r.Scraper, err = factory.TryGetScrapper(ctx, params) 35 if err != nil { 36 return nil, err 37 } 38 39 return r, nil 40 } 41 42 type GradleRunner struct { 43 params envs.Params 44 Scraper scraper.Scraper 45 } 46 47 func (r *GradleRunner) Run(ctx context.Context, execution testkube.Execution) (result testkube.ExecutionResult, err error) { 48 if r.Scraper != nil { 49 defer r.Scraper.Close() 50 } 51 52 output.PrintLogf("%s Preparing for test run", ui.IconTruck) 53 err = r.Validate(execution) 54 if err != nil { 55 return result, err 56 } 57 58 // check that the datadir exists 59 _, err = os.Stat(r.params.DataDir) 60 if errors.Is(err, os.ErrNotExist) { 61 output.PrintLogf("%s Datadir %s does not exist", ui.IconCross, r.params.DataDir) 62 return result, err 63 } 64 65 // TODO design it better for now just append variables as envs 66 envManager := env.NewManagerWithVars(execution.Variables) 67 envManager.GetReferenceVars(envManager.Variables) 68 69 // check settings.gradle or settings.gradle.kts files exist 70 directory := filepath.Join(r.params.DataDir, "repo", execution.Content.Repository.Path) 71 72 fileInfo, err := os.Stat(directory) 73 if err != nil { 74 return result, err 75 } 76 77 if !fileInfo.IsDir() { 78 output.PrintLogf("%s passing gradle test as single file not implemented yet", ui.IconCross) 79 return result, errors.Errorf("passing gradle test as single file not implemented yet") 80 } 81 82 settingsGradle := filepath.Join(directory, "settings.gradle") 83 settingsGradleKts := filepath.Join(directory, "settings.gradle.kts") 84 85 _, settingsGradleErr := os.Stat(settingsGradle) 86 _, settingsGradleKtsErr := os.Stat(settingsGradleKts) 87 if errors.Is(settingsGradleErr, os.ErrNotExist) && errors.Is(settingsGradleKtsErr, os.ErrNotExist) { 88 output.PrintLogf("%s no settings.gradle or settings.gradle.kts found", ui.IconCross) 89 return *result.Err(errors.New("no settings.gradle or settings.gradle.kts found")), nil 90 } 91 92 // determine the Gradle command to use 93 // pass additional executor arguments/flags to Gradle 94 gradleCommand, args := executor.MergeCommandAndArgs(execution.Command, execution.Args) 95 gradleWrapper := filepath.Join(directory, "gradlew") 96 _, err = os.Stat(gradleWrapper) 97 if gradleCommand == "gradle" && err == nil { 98 // then we use the wrapper instead 99 gradleCommand = "./gradlew" 100 } 101 102 var taskName string 103 task := strings.Split(execution.TestType, "/")[1] 104 if !strings.EqualFold(task, "project") { 105 // then use the test subtype as task name 106 taskName = task 107 } 108 109 runPath := directory 110 var project string 111 if execution.Content.Repository != nil && execution.Content.Repository.WorkingDir != "" { 112 runPath = filepath.Join(r.params.DataDir, "repo", execution.Content.Repository.WorkingDir) 113 project = directory 114 } 115 116 for i := len(args) - 1; i >= 0; i-- { 117 if taskName == "" && args[i] == "<taskName>" { 118 args = append(args[:i], args[i+1:]...) 119 continue 120 } 121 122 if project == "" && (args[i] == "-p" || args[i] == "<projectDir>") { 123 args = append(args[:i], args[i+1:]...) 124 continue 125 } 126 127 if args[i] == "<taskName>" { 128 args[i] = taskName 129 } 130 131 if args[i] == "<projectDir>" { 132 args[i] = project 133 } 134 135 args[i] = os.ExpandEnv(args[i]) 136 } 137 138 output.PrintEvent("Running task: "+task, project, gradleCommand, envManager.ObfuscateStringSlice(args)) 139 out, err := executor.Run(runPath, gradleCommand, envManager, args...) 140 out = envManager.ObfuscateSecrets(out) 141 142 var ls []string 143 _ = filepath.Walk("/data", func(path string, info fs.FileInfo, err error) error { 144 ls = append(ls, path) 145 return nil 146 }) 147 output.PrintEvent("/data content", ls) 148 149 if err == nil { 150 output.PrintLogf("%s Test execution passed", ui.IconCheckMark) 151 result.Status = testkube.ExecutionStatusPassed 152 } else { 153 output.PrintLogf("%s Test execution failed: %s", ui.IconCross, err.Error()) 154 result.Status = testkube.ExecutionStatusFailed 155 result.ErrorMessage = err.Error() 156 if strings.Contains(result.ErrorMessage, "exit status 1") { 157 // probably some tests have failed 158 result.ErrorMessage = "build failed with an exception" 159 } else { 160 // Gradle was unable to run at all 161 return result, nil 162 } 163 } 164 165 var rerr error 166 if execution.PostRunScript != "" && execution.ExecutePostRunScriptBeforeScraping { 167 output.PrintLog(fmt.Sprintf("%s Running post run script...", ui.IconCheckMark)) 168 169 if runPath == "" { 170 runPath = r.params.WorkingDir 171 } 172 173 if rerr = agent.RunScript(execution.PostRunScript, runPath); rerr != nil { 174 output.PrintLogf("%s Failed to execute post run script %s", ui.IconWarning, rerr) 175 } 176 } 177 178 // scrape artifacts first even if there are errors above 179 if r.params.ScrapperEnabled && execution.ArtifactRequest != nil && len(execution.ArtifactRequest.Dirs) != 0 { 180 output.PrintLogf("Scraping directories: %v with masks: %v", execution.ArtifactRequest.Dirs, execution.ArtifactRequest.Masks) 181 182 if err := r.Scraper.Scrape(ctx, execution.ArtifactRequest.Dirs, execution.ArtifactRequest.Masks, execution); err != nil { 183 return *result.WithErrors(err), nil 184 } 185 } 186 187 result.Output = string(out) 188 result.OutputType = "text/plain" 189 190 junitReportPath := filepath.Join(directory, "build", "test-results") 191 err = filepath.Walk(junitReportPath, func(path string, info os.FileInfo, err error) error { 192 if err != nil { 193 output.PrintLogf("%s Could not process reports: %s", ui.IconCross, err.Error()) 194 return err 195 } 196 197 if !info.IsDir() && filepath.Ext(path) == ".xml" { 198 suites, _ := junit.IngestFile(path) 199 for _, suite := range suites { 200 for _, test := range suite.Tests { 201 result.Steps = append( 202 result.Steps, 203 testkube.ExecutionStepResult{ 204 Name: fmt.Sprintf("%s - %s", suite.Name, test.Name), 205 Duration: test.Duration.String(), 206 Status: mapStatus(test.Status), 207 }) 208 } 209 } 210 } 211 212 return nil 213 }) 214 215 if err != nil { 216 return *result.Err(err), nil 217 } 218 219 if rerr != nil { 220 return *result.Err(rerr), nil 221 } 222 223 return result, nil 224 } 225 226 func mapStatus(in junit.Status) (out string) { 227 switch string(in) { 228 case "passed": 229 return string(testkube.PASSED_ExecutionStatus) 230 default: 231 return string(testkube.FAILED_ExecutionStatus) 232 } 233 } 234 235 // GetType returns runner type 236 func (r *GradleRunner) GetType() runner.Type { 237 return runner.TypeMain 238 } 239 240 // Validate checks if Execution has valid data in context of Gradle executor 241 func (r *GradleRunner) Validate(execution testkube.Execution) error { 242 243 if execution.Content == nil { 244 output.PrintLogf("%s Can't find any content to run in execution data", ui.IconCross) 245 return errors.Errorf("can't find any content to run in execution data: %+v", execution) 246 } 247 248 if execution.Content.Repository == nil { 249 output.PrintLogf("%s Gradle executor handles only repository based tests, but repository is nil", ui.IconCross) 250 return errors.Errorf("gradle executor handles only repository based tests, but repository is nil") 251 } 252 253 if execution.Content.Repository.Branch == "" && execution.Content.Repository.Commit == "" { 254 output.PrintLogf("%s Can't find branch or commit in params must use one or the other, repo %+v", ui.IconCross, execution.Content.Repository) 255 return errors.Errorf("can't find branch or commit in params must use one or the other, repo:%+v", execution.Content.Repository) 256 } 257 258 return nil 259 }