From 4b6c6e423cfccdb60329c055ac9a96164461cb8c Mon Sep 17 00:00:00 2001 From: Pavel Busko Date: Thu, 14 Apr 2022 14:10:32 +0200 Subject: [PATCH] added integration tests Co-authored-by: Gareth Evans --- .../docker_integration_test_executor.go | 35 +++- integration/integration_golang_test.go | 187 ++++++------------ .../golang-project1/cmd/helper/helper.go | 34 ++++ .../golang-project1/go.mod | 2 +- .../golang-project2/go.mod | 2 +- 5 files changed, 128 insertions(+), 132 deletions(-) create mode 100644 integration/testdata/TestGolangIntegration/golang-project1/cmd/helper/helper.go diff --git a/integration/docker_integration_test_executor.go b/integration/docker_integration_test_executor.go index e25f3154d..3ff11987f 100644 --- a/integration/docker_integration_test_executor.go +++ b/integration/docker_integration_test_executor.go @@ -7,8 +7,6 @@ import ( "archive/tar" "bytes" "fmt" - "github.com/magiconair/properties/assert" - "github.com/pkg/errors" "io" "io/ioutil" "math/rand" @@ -19,6 +17,9 @@ import ( "testing" "time" + "github.com/magiconair/properties/assert" + "github.com/pkg/errors" + "github.com/SAP/jenkins-library/pkg/command" "github.com/SAP/jenkins-library/pkg/log" ) @@ -42,6 +43,7 @@ type IntegrationTestDockerExecRunnerBundle struct { Environment map[string]string Setup []string Network string + ExecNoLogin bool } // IntegrationTestDockerExecRunner keeps the state of an instance of a docker runner @@ -56,6 +58,7 @@ type IntegrationTestDockerExecRunner struct { Setup []string Network string ContainerName string + ExecNoLogin bool } func givenThisContainer(t *testing.T, bundle IntegrationTestDockerExecRunnerBundle) IntegrationTestDockerExecRunner { @@ -70,6 +73,7 @@ func givenThisContainer(t *testing.T, bundle IntegrationTestDockerExecRunnerBund Environment: bundle.Environment, Setup: bundle.Setup, Network: bundle.Network, + ExecNoLogin: bundle.ExecNoLogin, ContainerName: containerName, } @@ -173,7 +177,13 @@ func setupPiperBinary(t *testing.T, testRunner IntegrationTestDockerExecRunner, } func (d *IntegrationTestDockerExecRunner) whenRunningPiperCommand(command string, parameters ...string) error { - args := []string{"exec", "--workdir", "/project", d.ContainerName, "/bin/sh", "-l", "/piper-wrapper", "/piper", command} + args := []string{"exec", "--workdir", "/project", d.ContainerName, "/bin/sh"} + + if !d.ExecNoLogin { + args = append(args, "-l") + } + + args = append(args, "/piper-wrapper", "/piper", command) args = append(args, parameters...) err := d.Runner.RunExecutable("docker", args...) if err != nil { @@ -184,10 +194,27 @@ func (d *IntegrationTestDockerExecRunner) whenRunningPiperCommand(command string } func (d *IntegrationTestDockerExecRunner) runScriptInsideContainer(script string) error { - args := []string{"exec", "--workdir", "/project", d.ContainerName, "/bin/sh", "-l", "-c", script} + args := []string{"exec", "--workdir", "/project", d.ContainerName, "/bin/sh"} + + if !d.ExecNoLogin { + args = append(args, "-l") + } + + args = append(args, "-c", script) return d.Runner.RunExecutable("docker", args...) } +func (d *IntegrationTestDockerExecRunner) assertHasNoOutput(t *testing.T, want string) { + buffer, err := d.getPiperOutput() + if err != nil { + t.Fatalf("Failed to get log output of container %s", d.ContainerName) + } + + if strings.Contains(buffer.String(), want) { + assert.Equal(t, buffer.String(), want, "Unexpected command output") + } +} + func (d *IntegrationTestDockerExecRunner) assertHasOutput(t *testing.T, want string) { buffer, err := d.getPiperOutput() if err != nil { diff --git a/integration/integration_golang_test.go b/integration/integration_golang_test.go index f334d5906..c997b03d1 100644 --- a/integration/integration_golang_test.go +++ b/integration/integration_golang_test.go @@ -6,157 +6,92 @@ package main import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" "testing" "github.com/stretchr/testify/assert" - "github.com/testcontainers/testcontainers-go" ) // In this test the piper command golangBuild performs testing, BOM file creation and building a project with entry point in the cmd/server/server.go // The configuration for golangBuild can be found in testdata/TestGolangIntegration/golang-project1/.pipeline/config.yml func TestGolangBuild_Project1(t *testing.T) { t.Parallel() - ctx := context.Background() - pwd, err := os.Getwd() - assert.NoError(t, err, "Getting current working directory failed.") - pwd = filepath.Dir(pwd) - - // using custom createTmpDir function to avoid issues with symlinks on Docker for Mac - tempDir, err := createTmpDir("") - defer os.RemoveAll(tempDir) // clean up - assert.NoError(t, err, "Error when creating temp dir") - - err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestGolangIntegration", "golang-project1"), tempDir) - if err != nil { - t.Fatal("Failed to copy test project.") - } - - //workaround to use test script util it is possible to set workdir for Exec call - testScript := fmt.Sprintf(`#!/bin/sh -cd /test -/piperbin/piper golangBuild >test-log.txt 2>&1 -`) - ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700) - - reqNode := testcontainers.ContainerRequest{ - Image: "golang:1", - Cmd: []string{"tail", "-f"}, - BindMounts: map[string]string{ - pwd: "/piperbin", - tempDir: "/test", - }, - } - - nodeContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: reqNode, - Started: true, + container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ + Image: "golang:1", + TestDir: []string{"testdata", "TestGolangIntegration", "golang-project1"}, + ExecNoLogin: true, }) - - code, err := nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"}) + err := container.whenRunningPiperCommand("golangBuild") assert.NoError(t, err) - assert.Equal(t, 0, code) - content, err := ioutil.ReadFile(filepath.Join(tempDir, "/test-log.txt")) - if err != nil { - t.Fatal("Could not read test-log.txt.", err) - } - output := string(content) - assert.Contains(t, output, "info golangBuild - running command: go install gotest.tools/gotestsum@latest") - assert.Contains(t, output, "info golangBuild - running command: go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest") - assert.Contains(t, output, "info golangBuild - running command: gotestsum --junitfile TEST-go.xml -- -coverprofile=cover.out ./...") - assert.Contains(t, output, "info golangBuild - DONE 8 tests") - assert.Contains(t, output, "info golangBuild - running command: go tool cover -html cover.out -o coverage.html") - assert.Contains(t, output, "info golangBuild - running command: gotestsum --junitfile TEST-integration.xml -- -tags=integration ./...") - assert.Contains(t, output, "info golangBuild - running command: cyclonedx-gomod mod -licenses -test -output bom.xml") - assert.Contains(t, output, "info golangBuild - running command: go build -trimpath -o golang-app-linux.amd64 cmd/server/server.go") - assert.Contains(t, output, "info golangBuild - SUCCESS") + container.assertHasOutput(t, "info golangBuild - running command: go install gotest.tools/gotestsum@latest") + container.assertHasOutput(t, "info golangBuild - running command: go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest") + container.assertHasOutput(t, "info golangBuild - running command: gotestsum --junitfile TEST-go.xml -- -coverprofile=cover.out ./...") + container.assertHasOutput(t, "info golangBuild - DONE 8 tests") + container.assertHasOutput(t, "info golangBuild - running command: go tool cover -html cover.out -o coverage.html") + container.assertHasOutput(t, "info golangBuild - running command: gotestsum --junitfile TEST-integration.xml -- -tags=integration ./...") + container.assertHasOutput(t, "info golangBuild - running command: cyclonedx-gomod mod -licenses -test -output bom.xml") + container.assertHasOutput(t, "info golangBuild - running command: go build -trimpath -o golang-app-linux.amd64 cmd/server/server.go") + container.assertHasOutput(t, "info golangBuild - SUCCESS") - //workaround to use test script util it is possible to set workdir for Exec call - testScript = fmt.Sprintf(`#!/bin/sh -cd /test -ls -l >files-list.txt 2>&1 -`) - ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700) + container.assertHasFile(t, "/project/TEST-go.xml") + container.assertHasFile(t, "/project/TEST-integration.xml") + container.assertHasFile(t, "/project/bom.xml") + container.assertHasFile(t, "/project/cover.out") + container.assertHasFile(t, "/project/coverage.html") + container.assertHasFile(t, "/project/golang-app-linux.amd64") +} - code, err = nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"}) +// This test extends TestGolangBuild_Project1 with multi-package build +func TestGolangBuild_Project1_Multipackage(t *testing.T) { + t.Parallel() + + container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ + Image: "golang:1", + TestDir: []string{"testdata", "TestGolangIntegration", "golang-project1"}, + ExecNoLogin: true, + }) + err := container.whenRunningPiperCommand("golangBuild", "--packages", "github.com/example/golang-app/cmd/server,github.com/example/golang-app/cmd/helper") assert.NoError(t, err) - assert.Equal(t, 0, code) - content, err = ioutil.ReadFile(filepath.Join(tempDir, "/files-list.txt")) - if err != nil { - t.Fatal("Could not read files-list.txt.", err) - } - output = string(content) - assert.Contains(t, output, "TEST-go.xml") - assert.Contains(t, output, "TEST-integration.xml") - assert.Contains(t, output, "bom.xml") - assert.Contains(t, output, "cover.out") - assert.Contains(t, output, "coverage.html") - assert.Contains(t, output, "golang-app-linux.amd64") + container.assertHasOutput(t, "info golangBuild - running command: go install gotest.tools/gotestsum@latest") + container.assertHasOutput(t, "info golangBuild - running command: go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest") + container.assertHasOutput(t, "info golangBuild - running command: gotestsum --junitfile TEST-go.xml -- -coverprofile=cover.out ./...") + container.assertHasOutput(t, "info golangBuild - DONE 8 tests") + container.assertHasOutput(t, "info golangBuild - running command: go tool cover -html cover.out -o coverage.html") + container.assertHasOutput(t, "info golangBuild - running command: gotestsum --junitfile TEST-integration.xml -- -tags=integration ./...") + container.assertHasOutput(t, "info golangBuild - running command: cyclonedx-gomod mod -licenses -test -output bom.xml") + container.assertHasOutput(t, "info golangBuild - running command: go build -trimpath -o golang-app-linux-amd64/ github.com/example/golang-app/cmd/server github.com/example/golang-app/cmd/helper") + container.assertHasOutput(t, "info golangBuild - SUCCESS") + + container.assertHasFile(t, "/project/TEST-go.xml") + container.assertHasFile(t, "/project/TEST-integration.xml") + container.assertHasFile(t, "/project/bom.xml") + container.assertHasFile(t, "/project/cover.out") + container.assertHasFile(t, "/project/coverage.html") + container.assertHasFile(t, "/project/golang-app-linux-amd64/server") + container.assertHasFile(t, "/project/golang-app-linux-amd64/helper") } // In this test, the piper golangBuild command only builds the project with the entry point at the project root. // The configuration for golangBuild can be found in testdata/TestGolangIntegration/golang-project2/.pipeline/config.yml func TestGolangBuild_Project2(t *testing.T) { t.Parallel() - ctx := context.Background() - pwd, err := os.Getwd() - assert.NoError(t, err, "Getting current working directory failed.") - pwd = filepath.Dir(pwd) - - // using custom createTmpDir function to avoid issues with symlinks on Docker for Mac - tempDir, err := createTmpDir("") - defer os.RemoveAll(tempDir) // clean up - assert.NoError(t, err, "Error when creating temp dir") - - err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestGolangIntegration", "golang-project2"), tempDir) - if err != nil { - t.Fatal("Failed to copy test project.") - } - - //workaround to use test script util it is possible to set workdir for Exec call - testScript := fmt.Sprintf(`#!/bin/sh -cd /test -/piperbin/piper golangBuild >test-log.txt 2>&1 -`) - ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700) - - reqNode := testcontainers.ContainerRequest{ - Image: "golang:1", - Cmd: []string{"tail", "-f"}, - BindMounts: map[string]string{ - pwd: "/piperbin", - tempDir: "/test", - }, - } - - nodeContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: reqNode, - Started: true, + container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ + Image: "golang:1", + TestDir: []string{"testdata", "TestGolangIntegration", "golang-project2"}, + ExecNoLogin: true, }) - - code, err := nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"}) + err := container.whenRunningPiperCommand("golangBuild") assert.NoError(t, err) - assert.Equal(t, 0, code) - content, err := ioutil.ReadFile(filepath.Join(tempDir, "/test-log.txt")) - if err != nil { - t.Fatal("Could not read test-log.txt.", err) - } - output := string(content) - assert.NotContains(t, output, "info golangBuild - running command: go install gotest.tools/gotestsum@latest") - assert.NotContains(t, output, "info golangBuild - running command: go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest") - assert.NotContains(t, output, "info golangBuild - running command: gotestsum --junitfile TEST-go.xml -- -coverprofile=cover.out ./...") - assert.NotContains(t, output, "info golangBuild - running command: go tool cover -html cover.out -o coverage.html") - assert.NotContains(t, output, "info golangBuild - running command: gotestsum --junitfile TEST-integration.xml -- -tags=integration ./...") - assert.NotContains(t, output, "info golangBuild - running command: cyclonedx-gomod mod -licenses -test -output bom.xml") - assert.Contains(t, output, "info golangBuild - running command: go build -trimpath -o golang-app-linux.amd64") - assert.Contains(t, output, "info golangBuild - SUCCESS") + container.assertHasNoOutput(t, "info golangBuild - running command: go install gotest.tools/gotestsum@latest") + container.assertHasNoOutput(t, "info golangBuild - running command: go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest") + container.assertHasNoOutput(t, "info golangBuild - running command: gotestsum --junitfile TEST-go.xml -- -coverprofile=cover.out ./...") + container.assertHasNoOutput(t, "info golangBuild - running command: go tool cover -html cover.out -o coverage.html") + container.assertHasNoOutput(t, "info golangBuild - running command: gotestsum --junitfile TEST-integration.xml -- -tags=integration ./...") + container.assertHasNoOutput(t, "info golangBuild - running command: cyclonedx-gomod mod -licenses -test -output bom.xml") + container.assertHasOutput(t, "info golangBuild - running command: go build -trimpath -o golang-app-linux.amd64") + container.assertHasOutput(t, "info golangBuild - SUCCESS") } diff --git a/integration/testdata/TestGolangIntegration/golang-project1/cmd/helper/helper.go b/integration/testdata/TestGolangIntegration/golang-project1/cmd/helper/helper.go new file mode 100644 index 000000000..130912a98 --- /dev/null +++ b/integration/testdata/TestGolangIntegration/golang-project1/cmd/helper/helper.go @@ -0,0 +1,34 @@ +package main + +import ( + "flag" + "fmt" + "net/http" + + "github.com/example/golang-app/pkg/http/handlers" + "github.com/example/golang-app/pkg/http/middlewares" + "github.com/gorilla/mux" +) + +const ( + defaultServerAddress = "0.0.0.0" + defaultServerPort = "8080" +) + +func main() { + serverAddress := flag.String("server.address", defaultServerAddress, "IP address of the HTTP server") + serverPort := flag.String("server.port", defaultServerPort, "Port of the HTTP server") + flag.Parse() + + router := mux.NewRouter() + + router.HandleFunc("/hello/{who}", middlewares.MultipleMiddleware(handlers.HelloHandler, + middlewares.LoggerMiddleware, middlewares.RecoverMiddleware)).Methods("GET", "POST") + router.HandleFunc("/panic", middlewares.MultipleMiddleware(handlers.ThrowPanicHandler, + middlewares.LoggerMiddleware, middlewares.RecoverMiddleware)).Methods("GET", "POST") + http.Handle("/", router) + + fmt.Printf("Server address: %s:%s\n", *serverAddress, *serverPort) + fmt.Println("Server is listening...") + http.ListenAndServe(fmt.Sprintf("%s:%s", *serverAddress, *serverPort), nil) +} diff --git a/integration/testdata/TestGolangIntegration/golang-project1/go.mod b/integration/testdata/TestGolangIntegration/golang-project1/go.mod index 88abffada..4f9ad187e 100644 --- a/integration/testdata/TestGolangIntegration/golang-project1/go.mod +++ b/integration/testdata/TestGolangIntegration/golang-project1/go.mod @@ -1,5 +1,5 @@ module github.com/example/golang-app -go 1.15 +go 1.18 require github.com/gorilla/mux v1.8.0 diff --git a/integration/testdata/TestGolangIntegration/golang-project2/go.mod b/integration/testdata/TestGolangIntegration/golang-project2/go.mod index a4869e618..3166825cd 100644 --- a/integration/testdata/TestGolangIntegration/golang-project2/go.mod +++ b/integration/testdata/TestGolangIntegration/golang-project2/go.mod @@ -1,3 +1,3 @@ module github.com/example/golang-app -go 1.15 +go 1.18