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  }