mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-04 04:07:16 +02:00
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>
This commit is contained in:
parent
b37f356eac
commit
b82ecb0ff7
114
cmd/batsExecuteTests.go
Normal file
114
cmd/batsExecuteTests.go
Normal file
@ -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
|
||||
|
||||
}
|
199
cmd/batsExecuteTests_generated.go
Normal file
199
cmd/batsExecuteTests_generated.go
Normal file
@ -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 ['<KEY1>=<VALUE1>','<KEY2>=<VALUE2>']. 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
|
||||
}
|
17
cmd/batsExecuteTests_generated_test.go
Normal file
17
cmd/batsExecuteTests_generated_test.go
Normal file
@ -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")
|
||||
|
||||
}
|
115
cmd/batsExecuteTests_test.go
Normal file
115
cmd/batsExecuteTests_test.go
Normal file
@ -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"))
|
||||
})
|
||||
}
|
@ -21,6 +21,7 @@ func GetAllStepMetadata() map[string]config.StepData {
|
||||
"abapEnvironmentCreateSystem": abapEnvironmentCreateSystemMetadata(),
|
||||
"abapEnvironmentPullGitRepo": abapEnvironmentPullGitRepoMetadata(),
|
||||
"abapEnvironmentRunATCCheck": abapEnvironmentRunATCCheckMetadata(),
|
||||
"batsExecuteTests": batsExecuteTestsMetadata(),
|
||||
"checkChangeInDevelopment": checkChangeInDevelopmentMetadata(),
|
||||
"checkmarxExecuteScan": checkmarxExecuteScanMetadata(),
|
||||
"cloudFoundryCreateService": cloudFoundryCreateServiceMetadata(),
|
||||
|
@ -138,6 +138,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(IntegrationArtifactUploadCommand())
|
||||
rootCmd.AddCommand(TerraformExecuteCommand())
|
||||
rootCmd.AddCommand(ContainerExecuteStructureTestsCommand())
|
||||
rootCmd.AddCommand(BatsExecuteTestsCommand())
|
||||
rootCmd.AddCommand(PipelineCreateScanSummaryCommand())
|
||||
|
||||
addRootFlags(rootCmd)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
69
resources/metadata/batsExecuteTests.yaml
Normal file
69
resources/metadata/batsExecuteTests.yaml
Normal file
@ -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 ['<KEY1>=<VALUE1>','<KEY2>=<VALUE2>']. 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
|
@ -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'))
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user