You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
Add golang implementation for karma tests (#919)
* Provide golang based karma step
This commit is contained in:
@@ -22,6 +22,6 @@ indent_size = none
|
||||
[cfg/id_rsa.enc]
|
||||
indent_style = none
|
||||
indent_size = none
|
||||
[{go.mod,go.sum,*.go}]
|
||||
[{go.mod,go.sum,*.go,*.golden}]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func openFileMock(name string) (io.ReadCloser, error) {
|
||||
func configOpenFileMock(name string) (io.ReadCloser, error) {
|
||||
var r string
|
||||
switch name {
|
||||
case "TestAddCustomDefaults_default1":
|
||||
@@ -52,7 +52,7 @@ func TestConfigCommand(t *testing.T) {
|
||||
|
||||
t.Run("Run", func(t *testing.T) {
|
||||
t.Run("Success case", func(t *testing.T) {
|
||||
configOptions.openFile = openFileMock
|
||||
configOptions.openFile = configOpenFileMock
|
||||
err := cmd.RunE(cmd, []string{})
|
||||
assert.NoError(t, err, "error occured but none expected")
|
||||
})
|
||||
|
6
cmd/interfaces.go
Normal file
6
cmd/interfaces.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package cmd
|
||||
|
||||
type execRunner interface {
|
||||
RunExecutable(e string, p ...string) error
|
||||
Dir(d string)
|
||||
}
|
35
cmd/karmaExecuteTests.go
Normal file
35
cmd/karmaExecuteTests.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func karmaExecuteTests(myKarmaExecuteTestsOptions karmaExecuteTestsOptions) error {
|
||||
c := command.Command{}
|
||||
return runKarma(myKarmaExecuteTestsOptions, &c)
|
||||
}
|
||||
|
||||
func runKarma(myKarmaExecuteTestsOptions karmaExecuteTestsOptions, command execRunner) error {
|
||||
installCommandTokens := tokenize(myKarmaExecuteTestsOptions.InstallCommand)
|
||||
command.Dir(myKarmaExecuteTestsOptions.ModulePath)
|
||||
err := command.RunExecutable(installCommandTokens[0], installCommandTokens[1:]...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to execute install command '%v'", myKarmaExecuteTestsOptions.InstallCommand)
|
||||
}
|
||||
|
||||
runCommandTokens := tokenize(myKarmaExecuteTestsOptions.RunCommand)
|
||||
command.Dir(myKarmaExecuteTestsOptions.ModulePath)
|
||||
err = command.RunExecutable(runCommandTokens[0], runCommandTokens[1:]...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to execute run command '%v'", myKarmaExecuteTestsOptions.RunCommand)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tokenize(command string) []string {
|
||||
return strings.Split(command, " ")
|
||||
}
|
87
cmd/karmaExecuteTests_generated.go
Normal file
87
cmd/karmaExecuteTests_generated.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type karmaExecuteTestsOptions struct {
|
||||
InstallCommand string `json:"installCommand,omitempty"`
|
||||
ModulePath string `json:"modulePath,omitempty"`
|
||||
RunCommand string `json:"runCommand,omitempty"`
|
||||
}
|
||||
|
||||
var myKarmaExecuteTestsOptions karmaExecuteTestsOptions
|
||||
var karmaExecuteTestsStepConfigJSON string
|
||||
|
||||
// KarmaExecuteTestsCommand Executes the Karma test runner
|
||||
func KarmaExecuteTestsCommand() *cobra.Command {
|
||||
metadata := karmaExecuteTestsMetadata()
|
||||
var createKarmaExecuteTestsCmd = &cobra.Command{
|
||||
Use: "karmaExecuteTests",
|
||||
Short: "Executes the Karma test runner",
|
||||
Long: `In this step the ([Karma test runner](http://karma-runner.github.io)) is executed.
|
||||
|
||||
The step is using the ` + "`" + `seleniumExecuteTest` + "`" + ` step to spin up two containers in a Docker network:
|
||||
|
||||
* a Selenium/Chrome container (` + "`" + `selenium/standalone-chrome` + "`" + `)
|
||||
* a NodeJS container (` + "`" + `node:8-stretch` + "`" + `)
|
||||
|
||||
In the Docker network, the containers can be referenced by the values provided in ` + "`" + `dockerName` + "`" + ` and ` + "`" + `sidecarName` + "`" + `, the default values are ` + "`" + `karma` + "`" + ` and ` + "`" + `selenium` + "`" + `. These values must be used in the ` + "`" + `hostname` + "`" + ` properties of the test configuration ([Karma](https://karma-runner.github.io/1.0/config/configuration-file.html) and [WebDriver](https://github.com/karma-runner/karma-webdriver-launcher#usage)).
|
||||
|
||||
!!! note
|
||||
In a Kubernetes environment, the containers both need to be referenced with ` + "`" + `localhost` + "`" + `.`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return PrepareConfig(cmd, &metadata, "karmaExecuteTests", &myKarmaExecuteTestsOptions, openPiperFile)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return karmaExecuteTests(myKarmaExecuteTestsOptions)
|
||||
},
|
||||
}
|
||||
|
||||
addKarmaExecuteTestsFlags(createKarmaExecuteTestsCmd)
|
||||
return createKarmaExecuteTestsCmd
|
||||
}
|
||||
|
||||
func addKarmaExecuteTestsFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&myKarmaExecuteTestsOptions.InstallCommand, "installCommand", "npm install --quiet", "The command that is executed to install the test tool.")
|
||||
cmd.Flags().StringVar(&myKarmaExecuteTestsOptions.ModulePath, "modulePath", ".", "Define the path of the module to execute tests on.")
|
||||
cmd.Flags().StringVar(&myKarmaExecuteTestsOptions.RunCommand, "runCommand", "npm run karma", "The command that is executed to start the tests.")
|
||||
|
||||
cmd.MarkFlagRequired("installCommand")
|
||||
cmd.MarkFlagRequired("modulePath")
|
||||
cmd.MarkFlagRequired("runCommand")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func karmaExecuteTestsMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "installCommand",
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
{
|
||||
Name: "modulePath",
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
{
|
||||
Name: "runCommand",
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
cmd/karmaExecuteTests_generated_test.go
Normal file
16
cmd/karmaExecuteTests_generated_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKarmaExecuteTestsCommand(t *testing.T) {
|
||||
|
||||
testCmd := KarmaExecuteTestsCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "karmaExecuteTests", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
65
cmd/karmaExecuteTests_test.go
Normal file
65
cmd/karmaExecuteTests_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockRunner struct {
|
||||
dir []string
|
||||
calls []execCall
|
||||
}
|
||||
|
||||
type execCall struct {
|
||||
exec string
|
||||
params []string
|
||||
}
|
||||
|
||||
func (m *mockRunner) Dir(d string) {
|
||||
m.dir = append(m.dir, d)
|
||||
}
|
||||
|
||||
func (m *mockRunner) RunExecutable(e string, p ...string) error {
|
||||
if e == "fail" {
|
||||
return fmt.Errorf("error case")
|
||||
}
|
||||
exec := execCall{exec: e, params: p}
|
||||
m.calls = append(m.calls, exec)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRunKarma(t *testing.T) {
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
opts := karmaExecuteTestsOptions{ModulePath: "./test", InstallCommand: "npm install test", RunCommand: "npm run test"}
|
||||
|
||||
e := mockRunner{}
|
||||
err := runKarma(opts, &e)
|
||||
|
||||
assert.NoError(t, err, "error occured but no error expected")
|
||||
|
||||
assert.Equal(t, e.dir[0], "./test", "install command dir incorrect")
|
||||
assert.Equal(t, e.calls[0], execCall{exec: "npm", params: []string{"install", "test"}}, "install command/params incorrect")
|
||||
|
||||
assert.Equal(t, e.dir[1], "./test", "run command dir incorrect")
|
||||
assert.Equal(t, e.calls[1], execCall{exec: "npm", params: []string{"run", "test"}}, "run command/params incorrect")
|
||||
|
||||
})
|
||||
|
||||
t.Run("error case install command", func(t *testing.T) {
|
||||
opts := karmaExecuteTestsOptions{ModulePath: "./test", InstallCommand: "fail install test", RunCommand: "npm run test"}
|
||||
|
||||
e := mockRunner{}
|
||||
err := runKarma(opts, &e)
|
||||
assert.Error(t, err, "error expected but none occcured")
|
||||
})
|
||||
|
||||
t.Run("error case run command", func(t *testing.T) {
|
||||
opts := karmaExecuteTestsOptions{ModulePath: "./test", InstallCommand: "npm install test", RunCommand: "fail run test"}
|
||||
|
||||
e := mockRunner{}
|
||||
err := runKarma(opts, &e)
|
||||
assert.Error(t, err, "error expected but none occcured")
|
||||
})
|
||||
}
|
55
cmd/piper.go
55
cmd/piper.go
@@ -1,11 +1,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -36,6 +39,16 @@ var generalConfig generalConfigOptions
|
||||
func Execute() {
|
||||
|
||||
rootCmd.AddCommand(ConfigCommand())
|
||||
|
||||
addRootFlags(rootCmd)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func addRootFlags(rootCmd *cobra.Command) {
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&generalConfig.customConfig, "customConfig", ".pipeline/config.yml", "Path to the pipeline configuration file")
|
||||
rootCmd.PersistentFlags().StringSliceVar(&generalConfig.defaultConfig, "defaultConfig", nil, "Default configurations, passed as path to yaml file")
|
||||
rootCmd.PersistentFlags().StringVar(&generalConfig.parametersJSON, "parametersJSON", os.Getenv("PIPER_parametersJSON"), "Parameters to be considered in JSON format")
|
||||
@@ -43,10 +56,46 @@ func Execute() {
|
||||
rootCmd.PersistentFlags().StringVar(&generalConfig.stepConfigJSON, "stepConfigJSON", os.Getenv("PIPER_stepConfigJSON"), "Step configuration in JSON format")
|
||||
rootCmd.PersistentFlags().BoolVarP(&generalConfig.verbose, "verbose", "v", false, "verbose output")
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// PrepareConfig reads step configuration from various sources and merges it (defaults, config file, flags, ...)
|
||||
func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName string, options interface{}, openFile func(s string) (io.ReadCloser, error)) error {
|
||||
|
||||
filters := metadata.GetParameterFilters()
|
||||
|
||||
flagValues := config.AvailableFlagValues(cmd, &filters)
|
||||
|
||||
var myConfig config.Config
|
||||
var stepConfig config.StepConfig
|
||||
|
||||
if len(generalConfig.stepConfigJSON) != 0 {
|
||||
// ignore config & defaults in favor of passed stepConfigJSON
|
||||
stepConfig = config.GetStepConfigWithJSON(flagValues, generalConfig.stepConfigJSON, filters)
|
||||
} else {
|
||||
// use config & defaults
|
||||
|
||||
//accept that config file and defaults cannot be loaded since both are not mandatory here
|
||||
customConfig, _ := openFile(generalConfig.customConfig)
|
||||
var defaultConfig []io.ReadCloser
|
||||
for _, f := range generalConfig.defaultConfig {
|
||||
//ToDo: support also https as source
|
||||
fc, _ := openFile(f)
|
||||
defaultConfig = append(defaultConfig, fc)
|
||||
}
|
||||
|
||||
var err error
|
||||
stepConfig, err = myConfig.GetStepConfig(flagValues, generalConfig.parametersJSON, customConfig, defaultConfig, filters, generalConfig.stageName, stepName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "retrieving step configuration failed")
|
||||
}
|
||||
}
|
||||
|
||||
confJSON, _ := json.Marshal(stepConfig.Config)
|
||||
json.Unmarshal(confJSON, &options)
|
||||
|
||||
config.MarkFlagsWithValue(cmd, stepConfig)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openPiperFile(name string) (io.ReadCloser, error) {
|
||||
|
110
cmd/piper_test.go
Normal file
110
cmd/piper_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type stepOptions struct {
|
||||
TestParam string `json:"testParam,omitempty"`
|
||||
}
|
||||
|
||||
func openFileMock(name string) (io.ReadCloser, error) {
|
||||
var r string
|
||||
switch name {
|
||||
case "testDefaults.yml":
|
||||
r = "general:\n testParam: testValue"
|
||||
case "testDefaultsInvalid.yml":
|
||||
r = "invalid yaml"
|
||||
default:
|
||||
r = ""
|
||||
}
|
||||
return ioutil.NopCloser(strings.NewReader(r)), nil
|
||||
}
|
||||
|
||||
func TestAddRootFlags(t *testing.T) {
|
||||
var testRootCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
addRootFlags(testRootCmd)
|
||||
|
||||
assert.NotNil(t, testRootCmd.Flag("customConfig"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("defaultConfig"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("parametersJSON"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("stageName"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("stepConfigJSON"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("verbose"), "expected flag not available")
|
||||
|
||||
}
|
||||
|
||||
func TestPrepareConfig(t *testing.T) {
|
||||
defaultsBak := generalConfig.defaultConfig
|
||||
generalConfig.defaultConfig = []string{"testDefaults.yml"}
|
||||
defer func() { generalConfig.defaultConfig = defaultsBak }()
|
||||
|
||||
t.Run("using stepConfigJSON", func(t *testing.T) {
|
||||
stepConfigJSONBak := generalConfig.stepConfigJSON
|
||||
generalConfig.stepConfigJSON = `{"testParam": "testValueJSON"}`
|
||||
defer func() { generalConfig.stepConfigJSON = stepConfigJSONBak }()
|
||||
testOptions := stepOptions{}
|
||||
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage")
|
||||
metadata := config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "testParam", Scope: []string{"GENERAL"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
PrepareConfig(testCmd, &metadata, "testStep", &testOptions, openFileMock)
|
||||
assert.Equal(t, "testValueJSON", testOptions.TestParam, "wrong value retrieved from config")
|
||||
})
|
||||
|
||||
t.Run("using config files", func(t *testing.T) {
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
testOptions := stepOptions{}
|
||||
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage")
|
||||
metadata := config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "testParam", Scope: []string{"GENERAL"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, openFileMock)
|
||||
assert.NoError(t, err, "no error expected but error occured")
|
||||
|
||||
//assert config
|
||||
assert.Equal(t, "testValue", testOptions.TestParam, "wrong value retrieved from config")
|
||||
|
||||
//assert that flag has been marked as changed
|
||||
testCmd.Flags().VisitAll(func(pflag *flag.Flag) {
|
||||
if pflag.Name == "testParam" {
|
||||
assert.True(t, pflag.Changed, "flag should be marked as changed")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("error case", func(t *testing.T) {
|
||||
generalConfig.defaultConfig = []string{"testDefaultsInvalid.yml"}
|
||||
testOptions := stepOptions{}
|
||||
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
metadata := config.StepData{}
|
||||
|
||||
err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, openFileMock)
|
||||
assert.Error(t, err, "error expected but none occured")
|
||||
})
|
||||
})
|
||||
}
|
134
pkg/command/command.go
Normal file
134
pkg/command/command.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Command defines the information required for executing a call to any executable
|
||||
type Command struct {
|
||||
dir string
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// Dir sets the working directory for the execution
|
||||
func (c *Command) Dir(d string) {
|
||||
c.dir = d
|
||||
}
|
||||
|
||||
// ExecCommand defines how to execute os commands
|
||||
var ExecCommand = exec.Command
|
||||
|
||||
// RunShell runs the specified command on the shell
|
||||
func (c *Command) RunShell(shell, script string) error {
|
||||
|
||||
_out, _err := prepareOut(c.Stdout, c.Stderr)
|
||||
|
||||
cmd := ExecCommand(shell)
|
||||
|
||||
cmd.Dir = c.dir
|
||||
in := bytes.Buffer{}
|
||||
in.Write([]byte(script))
|
||||
cmd.Stdin = &in
|
||||
|
||||
if err := runCmd(cmd, _out, _err); err != nil {
|
||||
return errors.Wrapf(err, "running shell script failed with %v", shell)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunExecutable runs the specified executable with parameters
|
||||
func (c *Command) RunExecutable(executable string, params ...string) error {
|
||||
|
||||
_out, _err := prepareOut(c.Stdout, c.Stderr)
|
||||
|
||||
cmd := ExecCommand(executable, params...)
|
||||
|
||||
if len(c.dir) > 0 {
|
||||
cmd.Dir = c.dir
|
||||
}
|
||||
|
||||
if err := runCmd(cmd, _out, _err); err != nil {
|
||||
return errors.Wrapf(err, "running command '%v' failed", executable)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCmd(cmd *exec.Cmd, _out, _err io.Writer) error {
|
||||
|
||||
stdout, stderr, err := cmdPipes(cmd)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting commmand pipes failed")
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "starting command failed")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
var errStdout, errStderr error
|
||||
|
||||
go func() {
|
||||
_, errStdout = io.Copy(_out, stdout)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, errStderr = io.Copy(_err, stderr)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
err = cmd.Wait()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cmd.Run() failed")
|
||||
}
|
||||
|
||||
if errStdout != nil || errStderr != nil {
|
||||
return fmt.Errorf("failed to capture stdout/stderr: '%v'/'%v'", errStdout, errStderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareOut(stdout, stderr io.Writer) (io.Writer, io.Writer) {
|
||||
|
||||
//ToDo: check use of multiwriter instead to always write into os.Stdout and os.Stdin?
|
||||
//stdout := io.MultiWriter(os.Stdout, &stdoutBuf)
|
||||
//stderr := io.MultiWriter(os.Stderr, &stderrBuf)
|
||||
|
||||
if stdout == nil {
|
||||
stdout = os.Stdout
|
||||
}
|
||||
if stderr == nil {
|
||||
stderr = os.Stderr
|
||||
}
|
||||
|
||||
return stdout, stderr
|
||||
}
|
||||
|
||||
func cmdPipes(cmd *exec.Cmd) (io.ReadCloser, io.ReadCloser, error) {
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "getting Stdout pipe failed")
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "getting Stderr pipe failed")
|
||||
}
|
||||
return stdout, stderr, nil
|
||||
}
|
182
pkg/command/command_test.go
Normal file
182
pkg/command/command_test.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//based on https://golang.org/src/os/exec/exec_test.go
|
||||
func helperCommand(command string, s ...string) (cmd *exec.Cmd) {
|
||||
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
||||
cs = append(cs, s...)
|
||||
cmd = exec.Command(os.Args[0], cs...)
|
||||
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func TestShellRun(t *testing.T) {
|
||||
|
||||
t.Run("test shell", func(t *testing.T) {
|
||||
ExecCommand = helperCommand
|
||||
defer func() { ExecCommand = exec.Command }()
|
||||
o := new(bytes.Buffer)
|
||||
e := new(bytes.Buffer)
|
||||
|
||||
s := Command{Stdout: o, Stderr: e}
|
||||
s.RunShell("/bin/bash", "myScript")
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
t.Run("stdin-stdout", func(t *testing.T) {
|
||||
expectedOut := "Stdout: command /bin/bash - Stdin: myScript\n"
|
||||
if oStr := o.String(); oStr != expectedOut {
|
||||
t.Errorf("expected: %v got: %v", expectedOut, oStr)
|
||||
}
|
||||
})
|
||||
t.Run("stderr", func(t *testing.T) {
|
||||
expectedErr := "Stderr: command /bin/bash\n"
|
||||
if eStr := e.String(); eStr != expectedErr {
|
||||
t.Errorf("expected: %v got: %v", expectedErr, eStr)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecutableRun(t *testing.T) {
|
||||
|
||||
t.Run("test shell", func(t *testing.T) {
|
||||
ExecCommand = helperCommand
|
||||
defer func() { ExecCommand = exec.Command }()
|
||||
o := new(bytes.Buffer)
|
||||
e := new(bytes.Buffer)
|
||||
|
||||
ex := Command{Stdout: o, Stderr: e}
|
||||
ex.RunExecutable("echo", []string{"foo bar", "baz"}...)
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
t.Run("stdin", func(t *testing.T) {
|
||||
expectedOut := "foo bar baz\n"
|
||||
if oStr := o.String(); oStr != expectedOut {
|
||||
t.Errorf("expected: %v got: %v", expectedOut, oStr)
|
||||
}
|
||||
})
|
||||
t.Run("stderr", func(t *testing.T) {
|
||||
expectedErr := "Stderr: command echo\n"
|
||||
if eStr := e.String(); eStr != expectedErr {
|
||||
t.Errorf("expected: %v got: %v", expectedErr, eStr)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrepareOut(t *testing.T) {
|
||||
|
||||
t.Run("os", func(t *testing.T) {
|
||||
s := Command{}
|
||||
_out, _err := prepareOut(s.Stdout, s.Stderr)
|
||||
|
||||
if _out != os.Stdout {
|
||||
t.Errorf("expected out to be os.Stdout")
|
||||
}
|
||||
|
||||
if _err != os.Stderr {
|
||||
t.Errorf("expected err to be os.Stderr")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom", func(t *testing.T) {
|
||||
o := bytes.NewBufferString("")
|
||||
e := bytes.NewBufferString("")
|
||||
s := Command{Stdout: o, Stderr: e}
|
||||
_out, _err := prepareOut(s.Stdout, s.Stderr)
|
||||
|
||||
expectOut := "Test out"
|
||||
expectErr := "Test err"
|
||||
_out.Write([]byte(expectOut))
|
||||
_err.Write([]byte(expectErr))
|
||||
|
||||
t.Run("out", func(t *testing.T) {
|
||||
if o.String() != expectOut {
|
||||
t.Errorf("expected: %v got: %v", expectOut, o.String())
|
||||
}
|
||||
})
|
||||
t.Run("err", func(t *testing.T) {
|
||||
if e.String() != expectErr {
|
||||
t.Errorf("expected: %v got: %v", expectErr, e.String())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCmdPipes(t *testing.T) {
|
||||
//cmd := helperCommand(t, "echo", "foo bar", "baz")
|
||||
cmd := helperCommand("echo", "foo bar", "baz")
|
||||
defer func() { ExecCommand = exec.Command }()
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
o, e, err := cmdPipes(cmd)
|
||||
t.Run("no error", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("error occured but no error expected")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("out pipe", func(t *testing.T) {
|
||||
if o == nil {
|
||||
t.Errorf("no pipe received")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("err pipe", func(t *testing.T) {
|
||||
if e == nil {
|
||||
t.Errorf("no pipe received")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//based on https://golang.org/src/os/exec/exec_test.go
|
||||
//this is not directly executed
|
||||
func TestHelperProcess(*testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||
return
|
||||
}
|
||||
defer os.Exit(0)
|
||||
|
||||
args := os.Args
|
||||
for len(args) > 0 {
|
||||
if args[0] == "--" {
|
||||
args = args[1:]
|
||||
break
|
||||
}
|
||||
args = args[1:]
|
||||
}
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No command\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
cmd, args := args[0], args[1:]
|
||||
switch cmd {
|
||||
case "/bin/bash":
|
||||
o, _ := ioutil.ReadAll(os.Stdin)
|
||||
fmt.Fprintf(os.Stdout, "Stdout: command %v - Stdin: %v\n", cmd, string(o))
|
||||
fmt.Fprintf(os.Stderr, "Stderr: command %v\n", cmd)
|
||||
case "echo":
|
||||
iargs := []interface{}{}
|
||||
for _, s := range args {
|
||||
iargs = append(iargs, s)
|
||||
}
|
||||
fmt.Println(iargs...)
|
||||
fmt.Fprintf(os.Stderr, "Stderr: command %v\n", cmd)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
|
||||
os.Exit(2)
|
||||
|
||||
}
|
||||
}
|
@@ -372,6 +372,5 @@ func TestGetContextDefaults(t *testing.T) {
|
||||
|
||||
//no assert since we just want to make sure that no panic occurs
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
312
pkg/generator/step-metadata.go
Normal file
312
pkg/generator/step-metadata.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
)
|
||||
|
||||
type stepInfo struct {
|
||||
CobraCmdFuncName string
|
||||
CreateCmdVar string
|
||||
FlagsFunc string
|
||||
Long string
|
||||
Metadata []config.StepParameters
|
||||
OSImport bool
|
||||
Short string
|
||||
StepFunc string
|
||||
StepName string
|
||||
}
|
||||
|
||||
//StepGoTemplate ...
|
||||
const stepGoTemplate = `package cmd
|
||||
|
||||
import (
|
||||
//"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type {{ .StepName }}Options struct {
|
||||
{{- range $key, $value := .Metadata }}
|
||||
{{ $value.Name | golangName }} {{ $value.Type }} ` + "`json:\"{{$value.Name}},omitempty\"`" + `{{end}}
|
||||
}
|
||||
|
||||
var my{{ .StepName | title}}Options {{.StepName}}Options
|
||||
var {{ .StepName }}StepConfigJSON string
|
||||
|
||||
// {{.CobraCmdFuncName}} {{.Short}}
|
||||
func {{.CobraCmdFuncName}}() *cobra.Command {
|
||||
metadata := {{ .StepName }}Metadata()
|
||||
var {{.CreateCmdVar}} = &cobra.Command{
|
||||
Use: "{{.StepName}}",
|
||||
Short: "{{.Short}}",
|
||||
Long: {{ $tick := "` + "`" + `" }}{{ $tick }}{{.Long | longName }}{{ $tick }},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return PrepareConfig(cmd, &metadata, "{{ .StepName }}", &my{{ .StepName | title}}Options, openPiperFile)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return {{.StepName}}(my{{ .StepName | title }}Options)
|
||||
},
|
||||
}
|
||||
|
||||
{{.FlagsFunc}}({{.CreateCmdVar}})
|
||||
return {{.CreateCmdVar}}
|
||||
}
|
||||
|
||||
func {{.FlagsFunc}}(cmd *cobra.Command) {
|
||||
{{- range $key, $value := .Metadata }}
|
||||
cmd.Flags().{{ $value.Type | flagType }}(&my{{ $.StepName | title }}Options.{{ $value.Name | golangName }}, "{{ $value.Name }}", {{ $value.Default }}, "{{ $value.Description }}"){{ end }}
|
||||
{{- printf "\n" }}
|
||||
{{- range $key, $value := .Metadata }}{{ if $value.Mandatory }}
|
||||
cmd.MarkFlagRequired("{{ $value.Name }}"){{ end }}{{ end }}
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func {{ .StepName }}Metadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{{- range $key, $value := .Metadata }}
|
||||
{
|
||||
Name: "{{ $value.Name }}",
|
||||
Scope: []string{{ "{" }}{{ range $notused, $scope := $value.Scope }}"{{ $scope }}",{{ end }}{{ "}" }},
|
||||
Type: "{{ $value.Type }}",
|
||||
Mandatory: {{ $value.Mandatory }},
|
||||
},{{ end }}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
||||
`
|
||||
|
||||
//StepTestGoTemplate ...
|
||||
const stepTestGoTemplate = `package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test{{.CobraCmdFuncName}}(t *testing.T) {
|
||||
|
||||
testCmd := {{.CobraCmdFuncName}}()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "{{.StepName}}", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
||||
`
|
||||
|
||||
func main() {
|
||||
|
||||
metadataPath := "./resources/metadata"
|
||||
|
||||
metadataFiles, err := metadataFiles(metadataPath)
|
||||
checkError(err)
|
||||
|
||||
err = processMetaFiles(metadataFiles, openMetaFile, fileWriter)
|
||||
checkError(err)
|
||||
|
||||
cmd := exec.Command("go", "fmt", "./cmd")
|
||||
err = cmd.Run()
|
||||
checkError(err)
|
||||
|
||||
}
|
||||
|
||||
func processMetaFiles(metadataFiles []string, openFile func(s string) (io.ReadCloser, error), writeFile func(filename string, data []byte, perm os.FileMode) error) error {
|
||||
for key := range metadataFiles {
|
||||
|
||||
var stepData config.StepData
|
||||
|
||||
configFilePath := metadataFiles[key]
|
||||
|
||||
metadataFile, err := openFile(configFilePath)
|
||||
checkError(err)
|
||||
defer metadataFile.Close()
|
||||
|
||||
fmt.Printf("Reading file %v\n", configFilePath)
|
||||
|
||||
err = stepData.ReadPipelineStepData(metadataFile)
|
||||
checkError(err)
|
||||
|
||||
fmt.Printf("Step name: %v\n", stepData.Metadata.Name)
|
||||
|
||||
err = setDefaultParameters(&stepData)
|
||||
checkError(err)
|
||||
|
||||
myStepInfo := getStepInfo(&stepData)
|
||||
|
||||
step := stepTemplate(myStepInfo)
|
||||
err = writeFile(fmt.Sprintf("cmd/%v_generated.go", stepData.Metadata.Name), step, 0644)
|
||||
checkError(err)
|
||||
|
||||
test := stepTestTemplate(myStepInfo)
|
||||
err = writeFile(fmt.Sprintf("cmd/%v_generated_test.go", stepData.Metadata.Name), test, 0644)
|
||||
checkError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func openMetaFile(name string) (io.ReadCloser, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
func fileWriter(filename string, data []byte, perm os.FileMode) error {
|
||||
return ioutil.WriteFile(filename, data, perm)
|
||||
}
|
||||
|
||||
func setDefaultParameters(stepData *config.StepData) error {
|
||||
//ToDo: custom function for default handling, support all relevant parameter types
|
||||
for k, param := range stepData.Spec.Inputs.Parameters {
|
||||
|
||||
if param.Default == nil {
|
||||
switch param.Type {
|
||||
case "string":
|
||||
param.Default = fmt.Sprintf("os.Getenv(\"PIPER_%v\")", param.Name)
|
||||
case "bool":
|
||||
// ToDo: Check if default should be read from env
|
||||
param.Default = "false"
|
||||
case "[]string":
|
||||
// ToDo: Check if default should be read from env
|
||||
param.Default = "[]string{}"
|
||||
default:
|
||||
return fmt.Errorf("Meta data type not set or not known: '%v'", param.Type)
|
||||
}
|
||||
} else {
|
||||
switch param.Type {
|
||||
case "string":
|
||||
param.Default = fmt.Sprintf("\"%v\"", param.Default)
|
||||
case "bool":
|
||||
boolVal := "false"
|
||||
if param.Default.(bool) == true {
|
||||
boolVal = "true"
|
||||
}
|
||||
param.Default = boolVal
|
||||
case "[]string":
|
||||
param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(param.Default.([]string), "\", \""))
|
||||
default:
|
||||
return fmt.Errorf("Meta data type not set or not known: '%v'", param.Type)
|
||||
}
|
||||
}
|
||||
|
||||
stepData.Spec.Inputs.Parameters[k] = param
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStepInfo(stepData *config.StepData) stepInfo {
|
||||
return stepInfo{
|
||||
StepName: stepData.Metadata.Name,
|
||||
CobraCmdFuncName: fmt.Sprintf("%vCommand", strings.Title(stepData.Metadata.Name)),
|
||||
CreateCmdVar: fmt.Sprintf("create%vCmd", strings.Title(stepData.Metadata.Name)),
|
||||
Short: stepData.Metadata.Description,
|
||||
Long: stepData.Metadata.LongDescription,
|
||||
Metadata: stepData.Spec.Inputs.Parameters,
|
||||
FlagsFunc: fmt.Sprintf("add%vFlags", strings.Title(stepData.Metadata.Name)),
|
||||
}
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Printf("Error occured: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func metadataFiles(sourceDirectory string) ([]string, error) {
|
||||
|
||||
var metadataFiles []string
|
||||
|
||||
err := filepath.Walk(sourceDirectory, func(path string, info os.FileInfo, err error) error {
|
||||
if filepath.Ext(path) == ".yaml" {
|
||||
metadataFiles = append(metadataFiles, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return metadataFiles, nil
|
||||
}
|
||||
return metadataFiles, nil
|
||||
}
|
||||
|
||||
func stepTemplate(myStepInfo stepInfo) []byte {
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"flagType": flagType,
|
||||
"golangName": golangName,
|
||||
"title": strings.Title,
|
||||
"longName": longName,
|
||||
}
|
||||
|
||||
tmpl, err := template.New("step").Funcs(funcMap).Parse(stepGoTemplate)
|
||||
checkError(err)
|
||||
|
||||
var generatedCode bytes.Buffer
|
||||
err = tmpl.Execute(&generatedCode, myStepInfo)
|
||||
checkError(err)
|
||||
|
||||
return generatedCode.Bytes()
|
||||
}
|
||||
|
||||
func stepTestTemplate(myStepInfo stepInfo) []byte {
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"flagType": flagType,
|
||||
"golangName": golangName,
|
||||
"title": strings.Title,
|
||||
}
|
||||
|
||||
tmpl, err := template.New("stepTest").Funcs(funcMap).Parse(stepTestGoTemplate)
|
||||
checkError(err)
|
||||
|
||||
var generatedCode bytes.Buffer
|
||||
err = tmpl.Execute(&generatedCode, myStepInfo)
|
||||
checkError(err)
|
||||
|
||||
return generatedCode.Bytes()
|
||||
}
|
||||
|
||||
func longName(long string) string {
|
||||
l := strings.ReplaceAll(long, "`", "` + \"`\" + `")
|
||||
l = strings.TrimSpace(l)
|
||||
return l
|
||||
}
|
||||
|
||||
func golangName(name string) string {
|
||||
properName := strings.Replace(name, "Api", "API", -1)
|
||||
properName = strings.Replace(properName, "Url", "URL", -1)
|
||||
properName = strings.Replace(properName, "Id", "ID", -1)
|
||||
properName = strings.Replace(properName, "Json", "JSON", -1)
|
||||
properName = strings.Replace(properName, "json", "JSON", -1)
|
||||
return strings.Title(properName)
|
||||
}
|
||||
|
||||
func flagType(paramType string) string {
|
||||
var theFlagType string
|
||||
switch paramType {
|
||||
case "bool":
|
||||
theFlagType = "BoolVar"
|
||||
case "string":
|
||||
theFlagType = "StringVar"
|
||||
case "[]string":
|
||||
theFlagType = "StringSliceVar"
|
||||
default:
|
||||
fmt.Printf("Meta data type not set or not known: '%v'\n", paramType)
|
||||
os.Exit(1)
|
||||
}
|
||||
return theFlagType
|
||||
}
|
227
pkg/generator/step-metadata_test.go
Normal file
227
pkg/generator/step-metadata_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func configOpenFileMock(name string) (io.ReadCloser, error) {
|
||||
meta1 := `metadata:
|
||||
name: testStep
|
||||
description: Test description
|
||||
longDescription: |
|
||||
Long Test description
|
||||
spec:
|
||||
inputs:
|
||||
params:
|
||||
- name: param0
|
||||
type: string
|
||||
description: param0 description
|
||||
default: val0
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
mandatory: true
|
||||
- name: param1
|
||||
type: string
|
||||
description: param1 description
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- name: param2
|
||||
type: string
|
||||
description: param1 description
|
||||
scope:
|
||||
- PARAMETERS
|
||||
mandatory: true
|
||||
`
|
||||
var r string
|
||||
switch name {
|
||||
case "test.yaml":
|
||||
r = meta1
|
||||
default:
|
||||
r = ""
|
||||
}
|
||||
return ioutil.NopCloser(strings.NewReader(r)), nil
|
||||
}
|
||||
|
||||
var files map[string][]byte
|
||||
|
||||
func writeFileMock(filename string, data []byte, perm os.FileMode) error {
|
||||
if files == nil {
|
||||
files = make(map[string][]byte)
|
||||
}
|
||||
files[filename] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestProcessMetaFiles(t *testing.T) {
|
||||
|
||||
processMetaFiles([]string{"test.yaml"}, configOpenFileMock, writeFileMock)
|
||||
|
||||
t.Run("step code", func(t *testing.T) {
|
||||
goldenFilePath := filepath.Join("testdata", t.Name()+"_generated.golden")
|
||||
expected, err := ioutil.ReadFile(goldenFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading %v", goldenFilePath)
|
||||
}
|
||||
assert.Equal(t, expected, files["cmd/testStep_generated.go"])
|
||||
})
|
||||
|
||||
t.Run("test code", func(t *testing.T) {
|
||||
goldenFilePath := filepath.Join("testdata", t.Name()+"_generated.golden")
|
||||
expected, err := ioutil.ReadFile(goldenFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading %v", goldenFilePath)
|
||||
}
|
||||
assert.Equal(t, expected, files["cmd/testStep_generated_test.go"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetDefaultParameters(t *testing.T) {
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
stepData := config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "string", Default: "val0"},
|
||||
{Name: "param1", Scope: []string{"STEPS"}, Type: "string"},
|
||||
{Name: "param2", Scope: []string{"STAGES"}, Type: "bool", Default: true},
|
||||
{Name: "param3", Scope: []string{"PARAMETERS"}, Type: "bool"},
|
||||
{Name: "param4", Scope: []string{"ENV"}, Type: "[]string", Default: []string{"val4_1", "val4_2"}},
|
||||
{Name: "param5", Scope: []string{"ENV"}, Type: "[]string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"\"val0\"",
|
||||
"os.Getenv(\"PIPER_param1\")",
|
||||
"true",
|
||||
"false",
|
||||
"[]string{\"val4_1\", \"val4_2\"}",
|
||||
"[]string{}",
|
||||
}
|
||||
|
||||
err := setDefaultParameters(&stepData)
|
||||
|
||||
assert.NoError(t, err, "error occured but none expected")
|
||||
|
||||
for k, v := range expected {
|
||||
assert.Equal(t, v, stepData.Spec.Inputs.Parameters[k].Default, fmt.Sprintf("default not correct for parameter %v", k))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error case", func(t *testing.T) {
|
||||
stepData := []config.StepData{
|
||||
{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "int", Default: 10},
|
||||
{Name: "param1", Scope: []string{"GENERAL"}, Type: "int"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param1", Scope: []string{"GENERAL"}, Type: "int"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range stepData {
|
||||
err := setDefaultParameters(&v)
|
||||
assert.Error(t, err, fmt.Sprintf("error expected but none occured for parameter %v", k))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetStepInfo(t *testing.T) {
|
||||
|
||||
stepData := config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "testStep",
|
||||
Description: "Test description",
|
||||
LongDescription: "Long Test description",
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "string", Default: "test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
myStepInfo := getStepInfo(&stepData)
|
||||
|
||||
assert.Equal(t, "testStep", myStepInfo.StepName, "StepName incorrect")
|
||||
assert.Equal(t, "TestStepCommand", myStepInfo.CobraCmdFuncName, "CobraCmdFuncName incorrect")
|
||||
assert.Equal(t, "createTestStepCmd", myStepInfo.CreateCmdVar, "CreateCmdVar incorrect")
|
||||
assert.Equal(t, "Test description", myStepInfo.Short, "Short incorrect")
|
||||
assert.Equal(t, "Long Test description", myStepInfo.Long, "Long incorrect")
|
||||
assert.Equal(t, stepData.Spec.Inputs.Parameters, myStepInfo.Metadata, "Metadata incorrect")
|
||||
assert.Equal(t, "addTestStepFlags", myStepInfo.FlagsFunc, "FlagsFunc incorrect")
|
||||
|
||||
}
|
||||
|
||||
func TestLongName(t *testing.T) {
|
||||
tt := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{input: "my long name with no ticks", expected: "my long name with no ticks"},
|
||||
{input: "my long name with `ticks`", expected: "my long name with ` + \"`\" + `ticks` + \"`\" + `"},
|
||||
}
|
||||
|
||||
for k, v := range tt {
|
||||
assert.Equal(t, v.expected, longName(v.input), fmt.Sprintf("wrong long name for run %v", k))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGolangName(t *testing.T) {
|
||||
tt := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{input: "testApi", expected: "TestAPI"},
|
||||
{input: "testUrl", expected: "TestURL"},
|
||||
{input: "testId", expected: "TestID"},
|
||||
{input: "testJson", expected: "TestJSON"},
|
||||
{input: "jsonTest", expected: "JSONTest"},
|
||||
}
|
||||
|
||||
for k, v := range tt {
|
||||
assert.Equal(t, v.expected, golangName(v.input), fmt.Sprintf("wrong golang name for run %v", k))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagType(t *testing.T) {
|
||||
tt := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{input: "bool", expected: "BoolVar"},
|
||||
{input: "string", expected: "StringVar"},
|
||||
{input: "[]string", expected: "StringSliceVar"},
|
||||
}
|
||||
|
||||
for k, v := range tt {
|
||||
assert.Equal(t, v.expected, flagType(v.input), fmt.Sprintf("wrong flag type for run %v", k))
|
||||
}
|
||||
}
|
76
pkg/generator/testdata/TestProcessMetaFiles/step_code_generated.golden
vendored
Normal file
76
pkg/generator/testdata/TestProcessMetaFiles/step_code_generated.golden
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type testStepOptions struct {
|
||||
Param0 string `json:"param0,omitempty"`
|
||||
Param1 string `json:"param1,omitempty"`
|
||||
Param2 string `json:"param2,omitempty"`
|
||||
}
|
||||
|
||||
var myTestStepOptions testStepOptions
|
||||
var testStepStepConfigJSON string
|
||||
|
||||
// TestStepCommand Test description
|
||||
func TestStepCommand() *cobra.Command {
|
||||
metadata := testStepMetadata()
|
||||
var createTestStepCmd = &cobra.Command{
|
||||
Use: "testStep",
|
||||
Short: "Test description",
|
||||
Long: `Long Test description`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return PrepareConfig(cmd, &metadata, "testStep", &myTestStepOptions, openPiperFile)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return testStep(myTestStepOptions)
|
||||
},
|
||||
}
|
||||
|
||||
addTestStepFlags(createTestStepCmd)
|
||||
return createTestStepCmd
|
||||
}
|
||||
|
||||
func addTestStepFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&myTestStepOptions.Param0, "param0", "val0", "param0 description")
|
||||
cmd.Flags().StringVar(&myTestStepOptions.Param1, "param1", os.Getenv("PIPER_param1"), "param1 description")
|
||||
cmd.Flags().StringVar(&myTestStepOptions.Param2, "param2", os.Getenv("PIPER_param2"), "param1 description")
|
||||
|
||||
cmd.MarkFlagRequired("param0")
|
||||
cmd.MarkFlagRequired("param2")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func testStepMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "param0",
|
||||
Scope: []string{"GENERAL","PARAMETERS",},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
{
|
||||
Name: "param1",
|
||||
Scope: []string{"PARAMETERS",},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
},
|
||||
{
|
||||
Name: "param2",
|
||||
Scope: []string{"PARAMETERS",},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
pkg/generator/testdata/TestProcessMetaFiles/test_code_generated.golden
vendored
Normal file
16
pkg/generator/testdata/TestProcessMetaFiles/test_code_generated.golden
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTestStepCommand(t *testing.T) {
|
||||
|
||||
testCmd := TestStepCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "testStep", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
67
resources/metadata/karma.yaml
Normal file
67
resources/metadata/karma.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
metadata:
|
||||
name: karmaExecuteTests
|
||||
description: Executes the Karma test runner
|
||||
longDescription: |
|
||||
In this step the ([Karma test runner](http://karma-runner.github.io)) is executed.
|
||||
|
||||
The step is using the `seleniumExecuteTest` step to spin up two containers in a Docker network:
|
||||
|
||||
* a Selenium/Chrome container (`selenium/standalone-chrome`)
|
||||
* a NodeJS container (`node:8-stretch`)
|
||||
|
||||
In the Docker network, the containers can be referenced by the values provided in `dockerName` and `sidecarName`, the default values are `karma` and `selenium`. These values must be used in the `hostname` properties of the test configuration ([Karma](https://karma-runner.github.io/1.0/config/configuration-file.html) and [WebDriver](https://github.com/karma-runner/karma-webdriver-launcher#usage)).
|
||||
|
||||
!!! note
|
||||
In a Kubernetes environment, the containers both need to be referenced with `localhost`.
|
||||
spec:
|
||||
inputs:
|
||||
resources:
|
||||
- name: buildDescriptor
|
||||
type: stash
|
||||
- name: tests
|
||||
type: stash
|
||||
params:
|
||||
- name: installCommand
|
||||
type: string
|
||||
description: The command that is executed to install the test tool.
|
||||
default: npm install --quiet
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: modulePath
|
||||
type: string
|
||||
description: Define the path of the module to execute tests on.
|
||||
default: '.'
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: runCommand
|
||||
type: string
|
||||
description: The command that is executed to start the tests.
|
||||
default: npm run karma
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
#outputs:
|
||||
containers:
|
||||
- name: maven
|
||||
image: maven:3.5-jdk-8
|
||||
volumeMounts:
|
||||
- mountPath: /dev/shm
|
||||
name: dev-shm
|
||||
sidecars:
|
||||
- image: selenium/standalone-chrome
|
||||
name: selenium
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- mountPath: /dev/shm
|
||||
name: dev-shm
|
Reference in New Issue
Block a user