From b82ecb0ff79d59f73c89493f19f45ea527ecc142 Mon Sep 17 00:00:00 2001 From: Mikalai Dzemidzenka <46974394+renort@users.noreply.github.com> Date: Thu, 29 Apr 2021 17:50:23 +0300 Subject: [PATCH] convert batsExecuteTests to go implementation (#2737) * convert batsExecuteTests to go implementation * added additional test cases, added container definition to batsExecuteTests.yaml * added influx, for junit added container definition * added parameter envVars Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> --- cmd/batsExecuteTests.go | 114 +++++++++++++ cmd/batsExecuteTests_generated.go | 199 +++++++++++++++++++++++ cmd/batsExecuteTests_generated_test.go | 17 ++ cmd/batsExecuteTests_test.go | 115 +++++++++++++ cmd/metadata_generated.go | 1 + cmd/piper.go | 1 + pkg/command/command.go | 16 ++ pkg/mock/runner.go | 10 ++ resources/metadata/batsExecuteTests.yaml | 69 ++++++++ test/groovy/BatsExecuteTestsTest.groovy | 171 ------------------- test/groovy/CommonStepsTest.groovy | 1 + vars/batsExecuteTests.groovy | 118 +------------- 12 files changed, 546 insertions(+), 286 deletions(-) create mode 100644 cmd/batsExecuteTests.go create mode 100644 cmd/batsExecuteTests_generated.go create mode 100644 cmd/batsExecuteTests_generated_test.go create mode 100644 cmd/batsExecuteTests_test.go create mode 100644 resources/metadata/batsExecuteTests.yaml delete mode 100644 test/groovy/BatsExecuteTestsTest.groovy diff --git a/cmd/batsExecuteTests.go b/cmd/batsExecuteTests.go new file mode 100644 index 000000000..6bd28f704 --- /dev/null +++ b/cmd/batsExecuteTests.go @@ -0,0 +1,114 @@ +package cmd + +import ( + "bytes" + "fmt" + "io" + "os" + + "github.com/SAP/jenkins-library/pkg/command" + pipergit "github.com/SAP/jenkins-library/pkg/git" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperutils" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/go-git/go-git/v5" + "github.com/pkg/errors" +) + +type batsExecuteTestsUtils interface { + CloneRepo(URL string) error + Stdin(in io.Reader) + Stdout(out io.Writer) + Stderr(err io.Writer) + SetEnv([]string) + FileWrite(path string, content []byte, perm os.FileMode) error + RunExecutable(e string, p ...string) error +} + +type batsExecuteTestsUtilsBundle struct { + *command.Command + *piperutils.Files +} + +func newBatsExecuteTestsUtils() batsExecuteTestsUtils { + utils := batsExecuteTestsUtilsBundle{ + Command: &command.Command{}, + Files: &piperutils.Files{}, + } + utils.Stdout(log.Writer()) + utils.Stderr(log.Writer()) + return &utils +} + +func batsExecuteTests(config batsExecuteTestsOptions, telemetryData *telemetry.CustomData, influx *batsExecuteTestsInflux) { + utils := newBatsExecuteTestsUtils() + + influx.step_data.fields.bats = false + err := runBatsExecuteTests(&config, telemetryData, utils) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } + influx.step_data.fields.bats = true +} + +func runBatsExecuteTests(config *batsExecuteTestsOptions, telemetryData *telemetry.CustomData, utils batsExecuteTestsUtils) error { + if config.OutputFormat != "tap" && config.OutputFormat != "junit" { + log.SetErrorCategory(log.ErrorConfiguration) + return fmt.Errorf("output format '%v' is incorrect. Possible drivers: tap, junit", config.OutputFormat) + } + + err := utils.CloneRepo(config.Repository) + + if err != nil && !errors.Is(err, git.ErrRepositoryAlreadyExists) { + return fmt.Errorf("couldn't pull %s repository: %w", config.Repository, err) + } + + tapOutput := bytes.Buffer{} + utils.Stdout(io.MultiWriter(&tapOutput, log.Writer())) + + utils.SetEnv(config.EnvVars) + err = utils.RunExecutable("bats-core/bin/bats", "--recursive", "--tap", config.TestPath) + if err != nil { + return fmt.Errorf("failed to run bats test: %w", err) + } + + err = utils.FileWrite("TEST-"+config.TestPackage+".tap", tapOutput.Bytes(), 0644) + if err != nil { + return fmt.Errorf("failed to write tap file: %w", err) + } + + if config.OutputFormat == "junit" { + output := bytes.Buffer{} + utils.Stdout(io.MultiWriter(&output, log.Writer())) + + utils.SetEnv(append(config.EnvVars, "NPM_CONFIG_PREFIX=~/.npm-global")) + err = utils.RunExecutable("npm", "install", "tap-xunit", "-g") + if err != nil { + return fmt.Errorf("failed to install tap-xunit: %w", err) + } + + homedir, _ := os.UserHomeDir() + path := "PATH=" + os.Getenv("PATH") + ":" + homedir + "/.npm-global/bin" + + output = bytes.Buffer{} + utils.Stdout(&output) + utils.Stdin(&tapOutput) + utils.SetEnv(append(config.EnvVars, path)) + err = utils.RunExecutable("tap-xunit", "--package="+config.TestPackage) + if err != nil { + return fmt.Errorf("failed to run tap-xunit: %w", err) + } + err = utils.FileWrite("TEST-"+config.TestPackage+".xml", output.Bytes(), 0644) + if err != nil { + return fmt.Errorf("failed to write tap file: %w", err) + } + } + + return nil +} + +func (b *batsExecuteTestsUtilsBundle) CloneRepo(URL string) error { + _, err := pipergit.PlainClone("", "", URL, "bats-core") + return err + +} diff --git a/cmd/batsExecuteTests_generated.go b/cmd/batsExecuteTests_generated.go new file mode 100644 index 000000000..a607196bc --- /dev/null +++ b/cmd/batsExecuteTests_generated.go @@ -0,0 +1,199 @@ +// Code generated by piper's step-generator. DO NOT EDIT. + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperenv" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/spf13/cobra" +) + +type batsExecuteTestsOptions struct { + OutputFormat string `json:"outputFormat,omitempty"` + Repository string `json:"repository,omitempty"` + TestPackage string `json:"testPackage,omitempty"` + TestPath string `json:"testPath,omitempty"` + EnvVars []string `json:"envVars,omitempty"` +} + +type batsExecuteTestsInflux struct { + step_data struct { + fields struct { + bats bool + } + tags struct { + } + } +} + +func (i *batsExecuteTestsInflux) persist(path, resourceName string) { + measurementContent := []struct { + measurement string + valType string + name string + value interface{} + }{ + {valType: config.InfluxField, measurement: "step_data", name: "bats", value: i.step_data.fields.bats}, + } + + errCount := 0 + for _, metric := range measurementContent { + err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(metric.measurement, fmt.Sprintf("%vs", metric.valType), metric.name), metric.value) + if err != nil { + log.Entry().WithError(err).Error("Error persisting influx environment.") + errCount++ + } + } + if errCount > 0 { + log.Entry().Fatal("failed to persist Influx environment") + } +} + +// BatsExecuteTestsCommand This step executes tests using the [Bash Automated Testing System - bats-core](https://github.com/bats-core/bats-core). +func BatsExecuteTestsCommand() *cobra.Command { + const STEP_NAME = "batsExecuteTests" + + metadata := batsExecuteTestsMetadata() + var stepConfig batsExecuteTestsOptions + var startTime time.Time + var influx batsExecuteTestsInflux + + var createBatsExecuteTestsCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "This step executes tests using the [Bash Automated Testing System - bats-core](https://github.com/bats-core/bats-core).", + Long: `Bats is a TAP-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected. A Bats test file is a Bash script with special syntax for defining test cases. Under the hood, each test case is just a function with a description.`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + + err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) + if err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return err + } + + if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { + sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) + log.RegisterHook(&sentryHook) + } + + return nil + }, + Run: func(_ *cobra.Command, _ []string) { + telemetryData := telemetry.CustomData{} + telemetryData.ErrorCode = "1" + handler := func() { + config.RemoveVaultSecretFiles() + influx.persist(GeneralConfig.EnvRootPath, "influx") + telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + telemetryData.ErrorCategory = log.GetErrorCategory().String() + telemetry.Send(&telemetryData) + } + log.DeferExitHandler(handler) + defer handler() + telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + batsExecuteTests(stepConfig, &telemetryData, &influx) + telemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addBatsExecuteTestsFlags(createBatsExecuteTestsCmd, &stepConfig) + return createBatsExecuteTestsCmd +} + +func addBatsExecuteTestsFlags(cmd *cobra.Command, stepConfig *batsExecuteTestsOptions) { + cmd.Flags().StringVar(&stepConfig.OutputFormat, "outputFormat", `junit`, "Defines the format of the test result output. junit would be the standard for automated build environments but you could use also the option tap.") + cmd.Flags().StringVar(&stepConfig.Repository, "repository", `https://github.com/bats-core/bats-core.git`, "Defines the version of bats-core to be used. By default we use the version from the master branch.") + cmd.Flags().StringVar(&stepConfig.TestPackage, "testPackage", `piper-bats`, "For the transformation of the test result to xUnit format the node module tap-xunit is used. This parameter defines the name of the test package used in the xUnit result file.") + cmd.Flags().StringVar(&stepConfig.TestPath, "testPath", `src/test`, "Defines either the directory which contains the test files (*.bats) or a single file. You can find further details in the Bats-core documentation.") + cmd.Flags().StringSliceVar(&stepConfig.EnvVars, "envVars", []string{}, "Injects environment variables to step execution. Format of value must be ['=','=']. Example: ['CONTAINER_NAME=piper-jenskins','IMAGE_NAME=my-image']") + +} + +// retrieve step metadata +func batsExecuteTestsMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "batsExecuteTests", + Aliases: []config.Alias{}, + Description: "This step executes tests using the [Bash Automated Testing System - bats-core](https://github.com/bats-core/bats-core).", + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Resources: []config.StepResources{ + {Name: "tests", Type: "stash"}, + }, + Parameters: []config.StepParameters{ + { + Name: "outputFormat", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"STEPS", "STAGES", "PARAMETERS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "repository", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"STEPS", "STAGES", "PARAMETERS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "testPackage", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"STEPS", "STAGES", "PARAMETERS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "testPath", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"STEPS", "STAGES", "PARAMETERS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + { + Name: "envVars", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"STEPS", "STAGES", "PARAMETERS"}, + Type: "[]string", + Mandatory: false, + Aliases: []config.Alias{}, + }, + }, + }, + Containers: []config.Container{ + {Name: "bats", Image: "node:lts-stretch", WorkingDir: "/home/node", Conditions: []config.Condition{{ConditionRef: "strings-equal", Params: []config.Param{{Name: "outputFormat", Value: "junit"}}}}}, + }, + Outputs: config.StepOutputs{ + Resources: []config.StepResources{ + { + Name: "influx", + Type: "influx", + Parameters: []map[string]interface{}{ + {"Name": "step_data"}, {"fields": []map[string]string{{"name": "bats"}}}, + }, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/batsExecuteTests_generated_test.go b/cmd/batsExecuteTests_generated_test.go new file mode 100644 index 000000000..c0f942778 --- /dev/null +++ b/cmd/batsExecuteTests_generated_test.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBatsExecuteTestsCommand(t *testing.T) { + t.Parallel() + + testCmd := BatsExecuteTestsCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "batsExecuteTests", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/batsExecuteTests_test.go b/cmd/batsExecuteTests_test.go new file mode 100644 index 000000000..4229ba72d --- /dev/null +++ b/cmd/batsExecuteTests_test.go @@ -0,0 +1,115 @@ +package cmd + +import ( + "fmt" + "testing" + + "github.com/SAP/jenkins-library/pkg/mock" + "github.com/go-git/go-git/v5" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +type batsExecuteTestsMockUtils struct { + *mock.ExecMockRunner + *mock.FilesMock +} + +func (b batsExecuteTestsMockUtils) CloneRepo(URL string) error { + if URL != "https://github.com/bats-core/bats-core.git" { + return git.ErrRepositoryNotExists + } + return nil +} + +func newBatsExecuteTestsTestsUtils() batsExecuteTestsMockUtils { + utils := batsExecuteTestsMockUtils{ + ExecMockRunner: &mock.ExecMockRunner{}, + FilesMock: &mock.FilesMock{}, + } + return utils +} + +func TestRunBatsExecuteTests(t *testing.T) { + t.Parallel() + t.Run("success case", func(t *testing.T) { + t.Parallel() + config := &batsExecuteTestsOptions{ + OutputFormat: "junit", + Repository: "https://github.com/bats-core/bats-core.git", + TestPackage: "piper-bats", + TestPath: "src/test", + } + + mockUtils := newBatsExecuteTestsTestsUtils() + err := runBatsExecuteTests(config, nil, &mockUtils) + assert.NoError(t, err) + assert.True(t, mockUtils.HasFile("TEST-"+config.TestPackage+".tap")) + assert.True(t, mockUtils.HasFile("TEST-"+config.TestPackage+".xml")) + }) + + t.Run("output tap case", func(t *testing.T) { + t.Parallel() + config := &batsExecuteTestsOptions{ + OutputFormat: "tap", + Repository: "https://github.com/bats-core/bats-core.git", + TestPackage: "piper-bats", + TestPath: "src/test", + } + + mockUtils := newBatsExecuteTestsTestsUtils() + err := runBatsExecuteTests(config, nil, &mockUtils) + assert.NoError(t, err) + assert.True(t, mockUtils.HasFile("TEST-"+config.TestPackage+".tap")) + assert.False(t, mockUtils.HasFile("TEST-"+config.TestPackage+".xml")) + }) + + t.Run("output format failed case", func(t *testing.T) { + t.Parallel() + config := &batsExecuteTestsOptions{ + OutputFormat: "fail", + Repository: "https://github.com/bats-core/bats-core.git", + TestPackage: "piper-bats", + TestPath: "src/test", + } + + mockUtils := newBatsExecuteTestsTestsUtils() + err := runBatsExecuteTests(config, nil, &mockUtils) + assert.EqualError(t, err, "output format 'fail' is incorrect. Possible drivers: tap, junit") + }) + + t.Run("failed to clone repo case", func(t *testing.T) { + t.Parallel() + config := &batsExecuteTestsOptions{ + OutputFormat: "junit", + Repository: "fail", + TestPackage: "piper-bats", + TestPath: "src/test", + } + + mockUtils := newBatsExecuteTestsTestsUtils() + err := runBatsExecuteTests(config, nil, &mockUtils) + + expectedError := fmt.Errorf("couldn't pull %s repository: %w", config.Repository, git.ErrRepositoryNotExists) + assert.EqualError(t, err, expectedError.Error()) + }) + + t.Run("failed to run bats case", func(t *testing.T) { + t.Parallel() + config := &batsExecuteTestsOptions{ + OutputFormat: "tap", + Repository: "https://github.com/bats-core/bats-core.git", + TestPackage: "piper-bats", + TestPath: "src/test", + } + + mockUtils := batsExecuteTestsMockUtils{ + ExecMockRunner: &mock.ExecMockRunner{ShouldFailOnCommand: map[string]error{"bats-core/bin/bats": errors.New("error case")}}, + FilesMock: &mock.FilesMock{}, + } + runBatsExecuteTests(config, nil, &mockUtils) + + assert.False(t, mockUtils.HasFile("TEST-"+config.TestPackage+".tap")) + assert.False(t, mockUtils.HasFile("TEST-"+config.TestPackage+".xml")) + }) +} diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go index b0201bd0a..a7ad66794 100644 --- a/cmd/metadata_generated.go +++ b/cmd/metadata_generated.go @@ -21,6 +21,7 @@ func GetAllStepMetadata() map[string]config.StepData { "abapEnvironmentCreateSystem": abapEnvironmentCreateSystemMetadata(), "abapEnvironmentPullGitRepo": abapEnvironmentPullGitRepoMetadata(), "abapEnvironmentRunATCCheck": abapEnvironmentRunATCCheckMetadata(), + "batsExecuteTests": batsExecuteTestsMetadata(), "checkChangeInDevelopment": checkChangeInDevelopmentMetadata(), "checkmarxExecuteScan": checkmarxExecuteScanMetadata(), "cloudFoundryCreateService": cloudFoundryCreateServiceMetadata(), diff --git a/cmd/piper.go b/cmd/piper.go index 0fa64eb55..2cca1c2f7 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -138,6 +138,7 @@ func Execute() { rootCmd.AddCommand(IntegrationArtifactUploadCommand()) rootCmd.AddCommand(TerraformExecuteCommand()) rootCmd.AddCommand(ContainerExecuteStructureTestsCommand()) + rootCmd.AddCommand(BatsExecuteTestsCommand()) rootCmd.AddCommand(PipelineCreateScanSummaryCommand()) addRootFlags(rootCmd) diff --git a/pkg/command/command.go b/pkg/command/command.go index 1ee8074a5..c8e1b0d2a 100644 --- a/pkg/command/command.go +++ b/pkg/command/command.go @@ -18,6 +18,7 @@ import ( type Command struct { ErrorCategoryMapping map[string][]string dir string + stdin io.Reader stdout io.Writer stderr io.Writer env []string @@ -28,6 +29,7 @@ type runner interface { SetDir(dir string) SetEnv(env []string) AppendEnv(env []string) + Stdin(in io.Reader) Stdout(out io.Writer) Stderr(err io.Writer) GetStdout() io.Writer @@ -62,6 +64,11 @@ func (c *Command) AppendEnv(env []string) { c.env = append(c.env, env...) } +// Stdin .. +func (c *Command) Stdin(stdin io.Reader) { + c.stdin = stdin +} + // Stdout .. func (c *Command) Stdout(stdout io.Writer) { c.stdout = stdout @@ -127,6 +134,10 @@ func (c *Command) RunExecutable(executable string, params ...string) error { appendEnvironment(cmd, c.env) + if c.stdin != nil { + cmd.Stdin = c.stdin + } + if err := c.runCmd(cmd); err != nil { return errors.Wrapf(err, "running command '%v' failed", executable) } @@ -150,6 +161,10 @@ func (c *Command) RunExecutableInBackground(executable string, params ...string) appendEnvironment(cmd, c.env) + if c.stdin != nil { + cmd.Stdin = c.stdin + } + execution, err := c.startCmd(cmd) if err != nil { @@ -360,5 +375,6 @@ func cmdPipes(cmd *exec.Cmd) (io.ReadCloser, io.ReadCloser, error) { if err != nil { return nil, nil, errors.Wrap(err, "getting Stderr pipe failed") } + return stdout, stderr, nil } diff --git a/pkg/mock/runner.go b/pkg/mock/runner.go index 0494be806..df14d4dfd 100644 --- a/pkg/mock/runner.go +++ b/pkg/mock/runner.go @@ -16,6 +16,7 @@ type ExecMockRunner struct { Env []string ExitCode int Calls []ExecCall + stdin io.Reader stdout io.Writer stderr io.Writer StdoutReturn map[string]string @@ -39,6 +40,7 @@ type ShellMockRunner struct { ExitCode int Calls []string Shell []string + stdin io.Reader stdout io.Writer stderr io.Writer StdoutReturn map[string]string @@ -86,6 +88,10 @@ func (m *ExecMockRunner) RunExecutableInBackground(e string, p ...string) (comma return &execution, nil } +func (m *ExecMockRunner) Stdin(in io.Reader) { + m.stdin = in +} + func (m *ExecMockRunner) Stdout(out io.Writer) { m.stdout = out } @@ -193,6 +199,10 @@ func handleCall(call string, stdoutReturn map[string]string, shouldFailOnCommand return nil } +func (m *ShellMockRunner) Stdin(in io.Reader) { + m.stdin = in +} + func (m *ShellMockRunner) Stdout(out io.Writer) { m.stdout = out } diff --git a/resources/metadata/batsExecuteTests.yaml b/resources/metadata/batsExecuteTests.yaml new file mode 100644 index 000000000..39f274c26 --- /dev/null +++ b/resources/metadata/batsExecuteTests.yaml @@ -0,0 +1,69 @@ +metadata: + name: batsExecuteTests + description: This step executes tests using the [Bash Automated Testing System - bats-core](https://github.com/bats-core/bats-core). + longDescription: | + Bats is a TAP-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected. A Bats test file is a Bash script with special syntax for defining test cases. Under the hood, each test case is just a function with a description. +spec: + inputs: + resources: + - name: tests + type: stash + params: + - name: outputFormat + type: string + description: Defines the format of the test result output. junit would be the standard for automated build environments but you could use also the option tap. + possibleValues: [tap, junit] + scope: + - STEPS + - STAGES + - PARAMETERS + default: "junit" + - name: repository + type: string + description: Defines the version of bats-core to be used. By default we use the version from the master branch. + scope: + - STEPS + - STAGES + - PARAMETERS + default: "https://github.com/bats-core/bats-core.git" + - name: testPackage + type: string + description: For the transformation of the test result to xUnit format the node module tap-xunit is used. This parameter defines the name of the test package used in the xUnit result file. + scope: + - STEPS + - STAGES + - PARAMETERS + default: "piper-bats" + - name: testPath + type: string + description: Defines either the directory which contains the test files (*.bats) or a single file. You can find further details in the Bats-core documentation. + scope: + - STEPS + - STAGES + - PARAMETERS + default: "src/test" + - name: envVars + type: "[]string" + description: "Injects environment variables to step execution. Format of value must be ['=','=']. Example: ['CONTAINER_NAME=piper-jenskins','IMAGE_NAME=my-image']" + scope: + - STEPS + - STAGES + - PARAMETERS + outputs: + resources: + - name: influx + type: influx + params: + - name: step_data + fields: + - name: bats + type: bool + containers: + - name: bats + image: node:lts-stretch + workingDir: /home/node + conditions: + - conditionRef: strings-equal + params: + - name: outputFormat + value: junit diff --git a/test/groovy/BatsExecuteTestsTest.groovy b/test/groovy/BatsExecuteTestsTest.groovy deleted file mode 100644 index c5256151e..000000000 --- a/test/groovy/BatsExecuteTestsTest.groovy +++ /dev/null @@ -1,171 +0,0 @@ -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.ExpectedException -import org.junit.rules.RuleChain -import util.* - -import static org.hamcrest.Matchers.hasItem -import static org.hamcrest.Matchers.is -import static org.hamcrest.Matchers.startsWith -import static org.junit.Assert.assertThat - -class BatsExecuteTestsTest extends BasePiperTest { - - private ExpectedException thrown = ExpectedException.none() - private JenkinsStepRule stepRule = new JenkinsStepRule(this) - private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this) - private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this) - private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this) - - @Rule - public RuleChain rules = Rules - .getCommonRules(this) - .around(new JenkinsReadYamlRule(this)) - .around(thrown) - .around(dockerExecuteRule) - .around(shellRule) - .around(loggingRule) - .around(stepRule) - - List withEnvArgs = [] - - @Before - void init() throws Exception { - helper.registerAllowedMethod("withEnv", [List.class, Closure.class], {arguments, closure -> - arguments.each {arg -> - withEnvArgs.add(arg.toString()) - } - return closure() - }) - } - - @Test - void testDefault() { - nullScript.commonPipelineEnvironment.configuration = [general: [container: 'test-container']] - stepRule.step.batsExecuteTests( - script: nullScript, - juStabUtils: utils, - dockerContainerName: 'test-container', - dockerImageNameAndTag: 'test/image', - envVars: [ - IMAGE_NAME: 'test/image', - CONTAINER_NAME: '${commonPipelineEnvironment.configuration.general.container}' - ], - testPackage: 'testPackage' - ) - // asserts - assertThat(withEnvArgs, hasItem('IMAGE_NAME=test/image')) - assertThat(withEnvArgs, hasItem('CONTAINER_NAME=test-container')) - assertThat(shellRule.shell, hasItem('git clone https://github.com/bats-core/bats-core.git')) - assertThat(shellRule.shell, hasItem('bats-core/bin/bats --recursive --tap src/test > \'TEST-testPackage.tap\'')) - assertThat(shellRule.shell, hasItem('cat \'TEST-testPackage.tap\'')) - - assertThat(dockerExecuteRule.dockerParams.dockerImage, is('node:lts-stretch')) - assertThat(dockerExecuteRule.dockerParams.dockerWorkspace, is('/home/node')) - - assertThat(shellRule.shell, hasItem('NPM_CONFIG_PREFIX=~/.npm-global npm install tap-xunit -g')) - assertThat(shellRule.shell, hasItem('cat \'TEST-testPackage.tap\' | PATH=\$PATH:~/.npm-global/bin tap-xunit --package=\'testPackage\' > TEST-testPackage.xml')) - - assertJobStatusSuccess() - } - - @Test - void testDockerFromCustomStepConfiguration() { - - def expectedImage = 'image:test' - def expectedEnvVars = ['env1': 'value1', 'env2': 'value2'] - def expectedOptions = '--opt1=val1 --opt2=val2 --opt3' - def expectedWorkspace = '/path/to/workspace' - - nullScript.commonPipelineEnvironment.configuration = [steps:[batsExecuteTests:[ - dockerImage: expectedImage, - dockerOptions: expectedOptions, - dockerEnvVars: expectedEnvVars, - dockerWorkspace: expectedWorkspace - ]]] - - stepRule.step.batsExecuteTests( - script: nullScript, - juStabUtils: utils - ) - - assert expectedImage == dockerExecuteRule.dockerParams.dockerImage - assert expectedOptions == dockerExecuteRule.dockerParams.dockerOptions - assert expectedEnvVars.equals(dockerExecuteRule.dockerParams.dockerEnvVars) - assert expectedWorkspace == dockerExecuteRule.dockerParams.dockerWorkspace - } - - @Test - void testTap() { - stepRule.step.batsExecuteTests( - script: nullScript, - juStabUtils: utils, - outputFormat: 'tap' - ) - assertThat(dockerExecuteRule.dockerParams.size(), is(0)) - } - - @Test - void testFailOnError() { - helper.registerAllowedMethod('sh', [String.class], {s -> - if (s.startsWith('bats-core/bin/bats')) { - throw new Exception('Shell call failed') - } else { - return null - } - }) - thrown.expectMessage('ERROR: The execution of the bats tests failed, see the log for details.') - stepRule.step.batsExecuteTests( - script: nullScript, - juStabUtils: utils, - failOnError: true, - ) - - } - - @Test - void testGit() { - def gitRepository - helper.registerAllowedMethod('git', [Map.class], {m -> - gitRepository = m - }) - helper.registerAllowedMethod('stash', [String.class], {s -> - assertThat(s, startsWith('testContent-')) - }) - - stepRule.step.batsExecuteTests( - script: nullScript, - juStabUtils: utils, - testRepository: 'testRepo', - ) - - assertThat(gitRepository.size(), is(1)) - assertThat(gitRepository.url, is('testRepo')) - assertThat(dockerExecuteRule.dockerParams.stashContent, hasItem(startsWith('testContent-'))) - } - - @Test - void testGitBranchAndCredentials() { - def gitRepository - helper.registerAllowedMethod('git', [Map.class], {m -> - gitRepository = m - }) - helper.registerAllowedMethod('stash', [String.class], {s -> - assertThat(s, startsWith('testContent-')) - }) - - stepRule.step.batsExecuteTests( - script: nullScript, - juStabUtils: utils, - gitBranch: 'test', - gitSshKeyCredentialsId: 'testCredentials', - testRepository: 'testRepo', - ) - assertThat(gitRepository.size(), is(3)) - assertThat(gitRepository.credentialsId, is('testCredentials')) - assertThat(gitRepository.branch, is('test')) - } - - -} diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 9dcb8eda8..64b082cc5 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -186,6 +186,7 @@ public class CommonStepsTest extends BasePiperTest{ 'integrationArtifactUpload', //implementing new golang pattern without fields 'containerExecuteStructureTests', //implementing new golang pattern without fields 'transportRequestUploadSOLMAN', //implementing new golang pattern without fields + 'batsExecuteTests', //implementing new golang pattern without fields ] @Test diff --git a/vars/batsExecuteTests.groovy b/vars/batsExecuteTests.groovy index 21605d0d4..c1a9bd11e 100644 --- a/vars/batsExecuteTests.groovy +++ b/vars/batsExecuteTests.groovy @@ -1,121 +1,9 @@ -import static com.sap.piper.Prerequisites.checkScript - -import com.sap.piper.GenerateDocumentation -import com.sap.piper.ConfigurationHelper -import com.sap.piper.GitUtils -import com.sap.piper.Utils -import com.sap.piper.analytics.InfluxData -import groovy.text.GStringTemplateEngine import groovy.transform.Field @Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/batsExecuteTests.yaml' -@Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS - -@Field Set STEP_CONFIG_KEYS = [ - /** @see dockerExecute */ - 'dockerImage', - /** @see dockerExecute */ - 'dockerEnvVars', - /** @see dockerExecute */ - 'dockerOptions', - /** @see dockerExecute */ - 'dockerWorkspace', - /** @see dockerExecute */ - 'stashContent', - /** Defines the environment variables to pass to the test execution.*/ - 'envVars', - /** Defines the behavior, in case tests fail. For example, in case of `outputFormat: 'junit'` you should set it to `false`. Otherwise test results cannot be recorded using the `testsPublishhResults` step afterwards.*/ - 'failOnError', - /** - * Defines the format of the test result output. `junit` would be the standard for automated build environments but you could use also the option `tap`. - * @possibleValues `junit`, `tap` - */ - 'outputFormat', - /** - * Defines the version of **bats-core** to be used. By default we use the version from the master branch. - */ - 'repository', - /** For the transformation of the test result to xUnit format the node module **tap-xunit** is used. This parameter defines the name of the test package used in the xUnit result file.*/ - 'testPackage', - /** Defines either the directory which contains the test files (`*.bats`) or a single file. You can find further details in the [Bats-core documentation](https://github.com/bats-core/bats-core#usage).*/ - 'testPath', - /** Allows to load tests from another repository.*/ - 'testRepository', - /** Defines the branch where the tests are located, in case the tests are not located in the master branch.*/ - 'gitBranch', - /** - * Defines the access credentials for protected repositories. - * Note: In case of using a protected repository, `testRepository` should include the ssh link to the repository. - */ - 'gitSshKeyCredentialsId' -] - -@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS - -/** This step executes tests using the [Bash Automated Testing System - bats-core](https://github.com/bats-core/bats-core)*/ -@GenerateDocumentation void call(Map parameters = [:]) { - handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) { - - def utils = parameters.juStabUtils ?: new Utils() - - def script = checkScript(this, parameters) ?: this - - String stageName = parameters.stageName ?: env.STAGE_NAME - - Map config = ConfigurationHelper.newInstance(this) - .loadStepDefaults([:], stageName) - .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS) - .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS) - .mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS) - .mixin(parameters, PARAMETER_KEYS) - .use() - - // report to SWA - utils.pushToSWA([ - step: STEP_NAME, - stepParamKey1: 'scriptMissing', - stepParam1: parameters?.script == null - ], config) - - InfluxData.addField('step_data', 'bats', false) - - config.stashContent = config.testRepository - ?[GitUtils.handleTestRepository(this, config)] - :utils.unstashAll(config.stashContent) - - //resolve commonPipelineEnvironment references in envVars - config.envVarList = [] - config.envVars.each {e -> - def envValue = GStringTemplateEngine.newInstance().createTemplate(e.getValue()).make(commonPipelineEnvironment: script.commonPipelineEnvironment).toString() - config.envVarList.add("${e.getKey()}=${envValue}") - } - - withEnv(config.envVarList) { - sh "git clone ${config.repository}" - try { - sh "bats-core/bin/bats --recursive --tap ${config.testPath} > 'TEST-${config.testPackage}.tap'" - InfluxData.addField('step_data', 'bats', true) - } catch (err) { - echo "[${STEP_NAME}] One or more tests failed" - if (config.failOnError) error "[${STEP_NAME}] ERROR: The execution of the bats tests failed, see the log for details." - } finally { - sh "cat 'TEST-${config.testPackage}.tap'" - if (config.outputFormat == 'junit') { - dockerExecute( - script: script, - dockerImage: config.dockerImage, - dockerEnvVars: config.dockerEnvVars, - dockerOptions: config.dockerOptions, - dockerWorkspace: config.dockerWorkspace, - stashContent: config.stashContent - ) { - sh "NPM_CONFIG_PREFIX=~/.npm-global npm install tap-xunit -g" - sh "cat 'TEST-${config.testPackage}.tap' | PATH=\$PATH:~/.npm-global/bin tap-xunit --package='${config.testPackage}' > TEST-${config.testPackage}.xml" - } - } - } - } - } + List credentials = [] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) }