1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-20 05:19:40 +02:00

Rewrite mta IT using docker cli (#1819)

This change addresses some issues of the testcontainer based testing approach (much repeated code, API not on the right abstraction level). It introduces new methods that make use of the docker cli, and rewrites the mta tests using this method.


Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
This commit is contained in:
Florian Wilhelm 2020-07-20 18:07:08 +02:00 committed by GitHub
parent 94dba13fef
commit 219327a427
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 179 additions and 175 deletions

View File

@ -0,0 +1,129 @@
// +build integration
package main
import (
"bytes"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"io/ioutil"
"math/rand"
"os"
"path"
"strconv"
"strings"
"testing"
"time"
)
// IntegrationTestDockerExecRunner keeps the state of an instance of a docker runner
type IntegrationTestDockerExecRunner struct {
// Runner is the ExecRunner to which all executions are forwarded in the end.
Runner command.Command
Image string
User string
TestDir []string
Mounts map[string]string
Environment map[string]string
Setup []string
ContainerName string
}
// IntegrationTestDockerExecRunnerBundle is used to construct an instance of IntegrationTestDockerExecRunner
type IntegrationTestDockerExecRunnerBundle struct {
Image string
User string
TestDir []string
Mounts map[string]string
Environment map[string]string
Setup []string
}
func givenThisContainer(t *testing.T, bundle IntegrationTestDockerExecRunnerBundle) IntegrationTestDockerExecRunner {
runner := command.Command{}
// Generate a random container name so we can start a new one for each test method
// We don't rely on docker's random name generator for two reasons
// First, it is easier to save the name here compared to getting it from stdout
// Second, the common prefix allows batch stopping/deleting of containers if so desired
// The test code will not automatically delete containers as they might be useful for debugging
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
containerName := "piper-integration-test-" + strconv.Itoa(seededRand.Int())
testRunner := IntegrationTestDockerExecRunner{
Runner: runner,
Image: bundle.Image,
User: bundle.User,
Mounts: bundle.Mounts,
Setup: bundle.Setup,
ContainerName: containerName,
}
//todo ensure it is a linux binary
wd, _ := os.Getwd()
localPiper := path.Join(wd, "..", "piper")
if localPiper == "" {
t.Fatal("Could not locate piper binary to test")
}
projectDir := path.Join(wd, path.Join(bundle.TestDir...))
// 1. Copy test files to a temp dir in order to avoid non-repeatable test executions because of changed state
// 2. Don't remove the temp dir to allow investigation of failed tests. Maybe add an option for cleaning it later?
tempDir, err := ioutil.TempDir("", "piper-integration-test")
if err != nil {
t.Fatal(err)
}
err = copyDir(projectDir, tempDir)
if err != nil {
t.Fatalf("")
}
//todo mounts
//todo env (secrets)
err = testRunner.Runner.RunExecutable("docker", "run", "-d", "-u="+testRunner.User,
"-v", localPiper+":/piper", "-v", tempDir+":/project",
"--name="+testRunner.ContainerName,
testRunner.Image,
"sleep", "2000")
if err != nil {
t.Fatalf("Starting test container has failed %s", err)
}
for _, scriptLine := range testRunner.Setup {
err := testRunner.Runner.RunExecutable("docker", "exec", testRunner.ContainerName, "/bin/bash", "-c", scriptLine)
if err != nil {
t.Fatalf("Running setup script in test container has failed %s", err)
}
}
err = testRunner.Runner.RunExecutable("docker", "cp", "piper-command-wrapper.sh", testRunner.ContainerName+":/piper-wrapper")
if err != nil {
t.Fatalf("Copying command wrapper to container has failed %s", err)
}
err = testRunner.Runner.RunExecutable("docker", "exec", testRunner.ContainerName, "chmod", "+x", "/piper-wrapper")
if err != nil {
t.Fatalf("Making command wrapper in container executable has failed %s", err)
}
return testRunner
}
func (d *IntegrationTestDockerExecRunner) whenRunningPiperCommand(command string, parameters ...string) error {
args := []string{"exec", "--workdir", "/project", d.ContainerName, "/bin/bash", "/piper-wrapper", "/piper", command}
args = append(args, parameters...)
return d.Runner.RunExecutable("docker", args...)
}
func (d *IntegrationTestDockerExecRunner) assertHasOutput(t *testing.T, want string) {
buffer := new(bytes.Buffer)
d.Runner.Stdout(buffer)
err := d.Runner.RunExecutable("docker", "exec", d.ContainerName, "cat", "/tmp/test-log.txt")
d.Runner.Stdout(log.Writer())
if err != nil {
t.Fatalf("Failed to get log output of container %s", d.ContainerName)
}
if !strings.Contains(buffer.String(), want) {
t.Fatalf("Assertion has failed. Expected output %s in command output.\n%s", want, buffer.String())
}
}

View File

@ -4,210 +4,79 @@
package main
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
)
func TestMavenProject(t *testing.T) {
t.Parallel()
ctx := context.Background()
pwd, err := os.Getwd()
if err != nil {
t.Fatalf("Getting current working directory failed: %v", err)
}
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
if err != nil {
t.Fatalf("Error when creating temp dir: %v", err)
}
err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestMtaIntegration", "maven"), 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 := `#!/bin/sh
cd /test
apt-get -yqq update; apt-get -yqq install make
curl -OL https://github.com/SAP/cloud-mta-build-tool/releases/download/v1.0.14/cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz
tar xzf cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz
curl -sL https://deb.nodesource.com/setup_12.x | bash -
apt-get install -yqq nodejs
mv mbt /usr/bin
mkdir mym2
/piperbin/piper mtaBuild --installArtifacts --m2Path=mym2 >test-log.txt 2>&1
`
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
reqNode := testcontainers.ContainerRequest{
Image: "maven:3-openjdk-8-slim",
Cmd: []string{"tail", "-f"},
BindMounts: map[string]string{
pwd: "/piperbin",
tempDir: "/test",
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "maven:3-openjdk-8-slim",
User: "root",
TestDir: []string{"testdata", "TestMtaIntegration", "maven"},
Mounts: map[string]string{},
Setup: []string{
"apt-get -yqq update; apt-get -yqq install make",
"curl -OL https://github.com/SAP/cloud-mta-build-tool/releases/download/v1.0.14/cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz",
"tar xzf cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz",
"mv mbt /usr/bin",
"curl -sL https://deb.nodesource.com/setup_12.x | bash -",
"apt-get install -yqq nodejs",
},
}
mbtContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: reqNode,
Started: true,
})
code, err := mbtContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
err := container.whenRunningPiperCommand("mtaBuild", "--installArtifacts", "--m2Path=mym2")
if err != nil {
t.Fatalf("Script returened error: %v", err)
t.Fatalf("Piper command failed %s", 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, "Installing /test/.flattened-pom.xml to /test/mym2/mygroup/mymvn/1.0-SNAPSHOT/mymvn-1.0-SNAPSHOT.pom")
assert.Contains(t, output, "Installing /test/app/target/mymvn-app-1.0-SNAPSHOT.war to /test/mym2/mygroup/mymvn-app/1.0-SNAPSHOT/mymvn-app-1.0-SNAPSHOT.war")
assert.Contains(t, output, "Installing /test/app/target/mymvn-app-1.0-SNAPSHOT-classes.jar to /test/mym2/mygroup/mymvn-app/1.0-SNAPSHOT/mymvn-app-1.0-SNAPSHOT-classes.jar")
assert.Contains(t, output, "added 2 packages from 3 contributors and audited 2 packages in")
container.assertHasOutput(t, "Installing /project/.flattened-pom.xml to /project/mym2/mygroup/mymvn/1.0-SNAPSHOT/mymvn-1.0-SNAPSHOT.pom")
container.assertHasOutput(t, "Installing /project/app/target/mymvn-app-1.0-SNAPSHOT.war to /project/mym2/mygroup/mymvn-app/1.0-SNAPSHOT/mymvn-app-1.0-SNAPSHOT.war")
container.assertHasOutput(t, "Installing /project/app/target/mymvn-app-1.0-SNAPSHOT-classes.jar to /project/mym2/mygroup/mymvn-app/1.0-SNAPSHOT/mymvn-app-1.0-SNAPSHOT-classes.jar")
container.assertHasOutput(t, "added 2 packages from 3 contributors and audited 2 packages in")
}
func TestNPMProject(t *testing.T) {
t.Parallel()
ctx := context.Background()
pwd, err := os.Getwd()
if err != nil {
t.Fatalf("Getting current working directory failed: %v", err)
}
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
if err != nil {
t.Fatalf("Error when creating temp dir: %v", err)
}
err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestMtaIntegration", "npm"), 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 := `#!/bin/sh
cd /test
apt-get -yqq update; apt-get -yqq install make
curl -OL https://github.com/SAP/cloud-mta-build-tool/releases/download/v1.0.14/cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz
tar xzf cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz
mv mbt /usr/bin
/piperbin/piper mtaBuild >test-log.txt 2>&1
`
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
reqNode := testcontainers.ContainerRequest{
Image: "node:12",
Cmd: []string{"tail", "-f"},
BindMounts: map[string]string{
pwd: "/piperbin",
tempDir: "/test",
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "node:12",
User: "root",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
Mounts: map[string]string{},
Setup: []string{
"apt-get -yqq update; apt-get -yqq install make",
"curl -OL https://github.com/SAP/cloud-mta-build-tool/releases/download/v1.0.14/cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz",
"tar xzf cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz",
"mv mbt /usr/bin",
},
}
mbtContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: reqNode,
Started: true,
})
code, err := mbtContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
err := container.whenRunningPiperCommand("mtaBuild", "")
if err != nil {
t.Fatalf("Script returened error: %v", err)
t.Fatalf("Piper command failed %s", 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 the MTA archive generated at: test-mta-js.mtar")
container.assertHasOutput(t, "INFO the MTA archive generated at: test-mta-js.mtar")
}
func TestNPMProjectInstallsDevDependencies(t *testing.T) {
t.Parallel()
ctx := context.Background()
pwd, err := os.Getwd()
if err != nil {
t.Fatalf("Getting current working directory failed: %v", err)
}
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
if err != nil {
t.Fatalf("Error when creating temp dir: %v", err)
}
err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestMtaIntegration", "npm-install-dev-dependencies"), 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 := `#!/bin/sh
cd /test
apt-get -yqq update; apt-get -yqq install make
curl -OL https://github.com/SAP/cloud-mta-build-tool/releases/download/v1.0.14/cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz
tar xzf cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz
mv mbt /usr/bin
/piperbin/piper mtaBuild --installArtifacts >test-log.txt 2>&1
`
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
reqNode := testcontainers.ContainerRequest{
Image: "node:12",
Cmd: []string{"tail", "-f"},
BindMounts: map[string]string{
pwd: "/piperbin",
tempDir: "/test",
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "node:12",
User: "root",
TestDir: []string{"testdata", "TestMtaIntegration", "npm-install-dev-dependencies"},
Mounts: map[string]string{},
Setup: []string{
"apt-get -yqq update; apt-get -yqq install make",
"curl -OL https://github.com/SAP/cloud-mta-build-tool/releases/download/v1.0.14/cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz",
"tar xzf cloud-mta-build-tool_1.0.14_Linux_amd64.tar.gz",
"mv mbt /usr/bin",
},
}
mbtContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: reqNode,
Started: true,
})
code, err := mbtContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
err := container.whenRunningPiperCommand("mtaBuild", "--installArtifacts")
if err != nil {
t.Fatalf("Script returened error: %v", err)
t.Fatalf("Piper command failed %s", 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, "added 2 packages in")
container.assertHasOutput(t, "added 2 packages in")
}

View File

@ -0,0 +1,6 @@
#!/bin/sh
# The purpose of this script is to run the binary inside a test container and to ensure its output is stored for assertions
# This is not very elegant, but I have so far not found a better way to save output of a command run via "docker exec"
"$@" >/tmp/test-log.txt 2>&1