1
0
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:
Mikalai Dzemidzenka 2021-04-29 17:50:23 +03:00 committed by GitHub
parent b37f356eac
commit b82ecb0ff7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 546 additions and 286 deletions

114
cmd/batsExecuteTests.go Normal file
View 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
}

View 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
}

View 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")
}

View 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"))
})
}

View File

@ -21,6 +21,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"abapEnvironmentCreateSystem": abapEnvironmentCreateSystemMetadata(),
"abapEnvironmentPullGitRepo": abapEnvironmentPullGitRepoMetadata(),
"abapEnvironmentRunATCCheck": abapEnvironmentRunATCCheckMetadata(),
"batsExecuteTests": batsExecuteTestsMetadata(),
"checkChangeInDevelopment": checkChangeInDevelopmentMetadata(),
"checkmarxExecuteScan": checkmarxExecuteScanMetadata(),
"cloudFoundryCreateService": cloudFoundryCreateServiceMetadata(),

View File

@ -138,6 +138,7 @@ func Execute() {
rootCmd.AddCommand(IntegrationArtifactUploadCommand())
rootCmd.AddCommand(TerraformExecuteCommand())
rootCmd.AddCommand(ContainerExecuteStructureTestsCommand())
rootCmd.AddCommand(BatsExecuteTestsCommand())
rootCmd.AddCommand(PipelineCreateScanSummaryCommand())
addRootFlags(rootCmd)

View File

@ -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
}

View File

@ -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
}

View 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

View File

@ -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'))
}
}

View File

@ -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

View File

@ -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)
}