Skip to content

Commit

Permalink
Feature: integration tests with different runtimes (#23)
Browse files Browse the repository at this point in the history
* chore(*): tune gitignore

Signed-off-by: ArtemTrofimushkin <[email protected]>

* feat(sample): add web project as target app

Signed-off-by: ArtemTrofimushkin <[email protected]>

* feat(sample): add dockerfile

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(tests): extract common fixtures logic to separate method

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(tests): replace prebuilt target container with custom sample

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(tests): cleanup scripts

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(tests): add ability to specify target framework & override image in tests

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(ci): add matrix to gha build

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(tests): extend timeout

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(ci): fix pipeline

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(tests): fix tags

Signed-off-by: ArtemTrofimushkin <[email protected]>

* fix(sample): set different target frameworks property

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(ci): disable fail-fast

Signed-off-by: ArtemTrofimushkin <[email protected]>

* fix(tests): try to fix concurrent testcases

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(kubernetes): add container state description to pod waiting in case of error

Signed-off-by: ArtemTrofimushkin <[email protected]>

* chore(tests): use builder to generate testcases

Signed-off-by: ArtemTrofimushkin <[email protected]>

* fix(kubernetes): use correct fields for error message generation

Signed-off-by: ArtemTrofimushkin <[email protected]>

* fix(tests): try to fix parallel execution

Signed-off-by: ArtemTrofimushkin <[email protected]>

* fix(cli): use better error message in case of failure

Signed-off-by: ArtemTrofimushkin <[email protected]>

* fix(tests): simplify test execution

Signed-off-by: ArtemTrofimushkin <[email protected]>

* fix(tests): lower parallel degree

Signed-off-by: ArtemTrofimushkin <[email protected]>
  • Loading branch information
ArtemTrofimushkin authored Mar 25, 2022
1 parent 4c04363 commit fd12c59
Show file tree
Hide file tree
Showing 24 changed files with 607 additions and 368 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ jobs:
integration-tests:
name: Integration tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
framework:
- netcoreapp3.1
- net5.0
- net6.0
needs:
- doc
- lint
Expand Down Expand Up @@ -135,7 +142,7 @@ jobs:
image: kindest/node:v1.21.1
- name: Run tests
run: |
make test-integration
make test-integration FRAMEWORK=${{ matrix.framework }}
publish-dumper:
name: Publishing dumper docker image
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ trash/
vendor/
dist/
**/bin/**
**/obj/**

# Binaries for programs and plugins
*.exe
Expand Down
32 changes: 17 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
WHITE := $(shell tput -Txterm setaf 7)
RESET := $(shell tput -Txterm sgr0)
MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIR := $(dir $(MAKEFILE_PATH))
ARCH := $(shell uname -m)

.PHONY: all
all: help

.PHONY: prepare
prepare: tidy lint doc build test

.PHONY: cover
cover:
go tool cover -html ./cover.out

.PHONY: doc
doc:
./hacks/run-doc-generation.sh
doc: build-cli
cd cli && HOME="/home/user" ./bin/kubectl-shovel doc && cd -

.PHONY: lint
lint:
Expand All @@ -34,28 +39,25 @@ build-cli:
build-dumper:
go build -v -o ./dumper/bin/dumper ./dumper

.PHONY: setup
setup:
kind create cluster

.PHONY: test
test: test-unit test-integration

.PHONY: test-unit
test-unit:
TEST_RUN_ARGS="$(TEST_RUN_ARGS)" TEST_DIR="$(TEST_DIR)" ./hacks/run-unit-tests.sh

.PHONY: test-integration-setup
test-integration-setup:
kind create cluster --name "kind"

.PHONY: test-integration-prepare
test-integration-prepare:
./hacks/prepare-integration-tests.sh "$(ARCH)"
./hacks/prepare-integration-tests.sh "$(CURRENT_DIR)" "kind-kind" "$(ARCH)" "$(FRAMEWORK)"

.PHONY: test-integration
test-integration:
./hacks/prepare-integration-tests.sh "$(ARCH)"
./hacks/run-integration-tests.sh

.PHONY: prepare
prepare: tidy lint doc build-cli build-dumper test
./hacks/prepare-integration-tests.sh "$(CURRENT_DIR)" "kind-kind" "$(ARCH)" "$(FRAMEWORK)"
./hacks/run-integration-tests.sh "$(FRAMEWORK)"

.PHONY: help
help:
Expand All @@ -64,16 +66,16 @@ help:
@echo ' ${YELLOW}make${RESET} ${GREEN}<target>${RESET}'
@echo ''
@echo 'Targets:'
@echo " ${YELLOW}prepare ${RESET} Run all available checks and generators"
@echo " ${YELLOW}cover ${RESET} Open html coverage report in browser"
@echo " ${YELLOW}doc ${RESET} Run doc generation"
@echo " ${YELLOW}lint ${RESET} Run linters via golangci-lint"
@echo " ${YELLOW}tidy ${RESET} Run tidy for go module to remove unused dependencies"
@echo " ${YELLOW}build ${RESET} Build all components"
@echo " ${YELLOW}build-cli ${RESET} Build cli component of shovel"
@echo " ${YELLOW}build-dumper ${RESET} Build dumper component of shovel"
@echo " ${YELLOW}setup ${RESET} Setup local environment. Create kind cluster"
@echo " ${YELLOW}test ${RESET} Run all available tests"
@echo " ${YELLOW}test-unit ${RESET} Run all unit tests"
@echo " ${YELLOW}test-integration ${RESET} Run all integration tests"
@echo " ${YELLOW}test-integration-prepare ${RESET} Prepare integration tests (build dumper and load to kind cluster)"
@echo " ${YELLOW}prepare ${RESET} Run all available checks and generators"
@echo " ${YELLOW}test-integration-setup ${RESET} Setup integration tests environment. Create kind cluster"
@echo " ${YELLOW}test-integration-prepare ${RESET} Prepare integration tests (build required images and load to kind cluster)"
4 changes: 2 additions & 2 deletions cli/cmd/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (cb *CommandBuilder) launch() error {
fmt.Println("Waiting for a diagnostics job to start")
jobPod, err := cb.kube.WaitPod(jobSpec.Selectors)
if err != nil {
return errors.Wrap(err, "Failed to wait diagnostics job execution")
return errors.Wrap(err, "Failed to start diagnostics job")
}

jobPodLogs, err := cb.kube.ReadPodLogs(jobPod.Name, globals.PluginName)
Expand All @@ -128,7 +128,7 @@ func (cb *CommandBuilder) launch() error {
awaiter := events.NewEventAwaiter()
output, err := awaiter.AwaitCompletedEvent(jobPodLogs)
if err != nil {
return err
return errors.Wrap(err, "Failed to complete diagnostics job")
}

// dealing with output
Expand Down
102 changes: 75 additions & 27 deletions hacks/prepare-integration-tests.sh
Original file line number Diff line number Diff line change
@@ -1,45 +1,93 @@
#!/bin/bash
set -o errexit
#!/usr/bin/env bash
set -eu

script_dir="$(dirname "${BASH_SOURCE[0]}")"
project_dir="${script_dir}/.."
[ $# -lt 4 ] && echo "Usage: $(basename $0) <directory> <context> <arch|[amd64, arm64]> <framework|[netcoreapp3.1, net5.0, net6.0]>" && exit 1

kind_context="kind-kind"
current_context=$(kubectl config current-context)
directory=$1
context=$2
arch=$3
framework=$4

if [ "${current_context}" != "${kind_context}" ]; then
echo "Your context is wrong. Use ${kind_context}"
current_context=$(kubectl config current-context)
if [ "${current_context}" != "$context" ]; then
echo "Current context must be $context. Set current context with command \"kubectl config set-context $context\""
exit 1
fi

arch=${1}
cluster_os="linux"
if [ "$arch" == "x86_64" ]; then
arch="amd64"
cluster_arch="amd64"
elif [ "$arch" == "arm64" ]; then
cluster_arch="arm64"
else
echo "Unsupported arch $arch, choose from: x86_64 or arm64"
exit 1
fi

if [ "$arch" != "amd64" ] && [ "$arch" != "arm64" ]; then
echo "Unsupported arch, choose from: amd64 or arm64"
# dumper options
dumper_image_tag="latest"
dumper_image_repository="kubectl-shovel/dumper-integration-tests"
dumper_context="${directory}/dumper"
dumper_binary="$dumper_context/bin/dumper"

# sample app options
sample_image_tag="$framework"
sample_image_repository="kubectl-shovel/sample-integration-tests"
sample_context="${directory}/sample"

if [ "$framework" == "netcoreapp3.1" ]; then
sample_sdk_image_tag="3.1-focal"
sample_runtime_image_tag="3.1.23-focal"
elif [ "$framework" == "net5.0" ]; then
sample_sdk_image_tag="5.0.406-focal"
sample_runtime_image_tag="5.0.15-focal"
elif [ "$framework" == "net6.0" ]; then
sample_sdk_image_tag="6.0-focal"
sample_runtime_image_tag="6.0.3-focal"
else
echo "Unsupported .net target framework $framework specified, choose from: netcoreapp3.1, net5.0 or net6.0"
exit 1
fi

echo "Building dumper..."
GOOS=linux GOARCH=$arch CGO_ENABLED=0 \
go build -v \
-o ${project_dir}/dumper/bin/dumper \
${project_dir}/dumper

image_tag="latest"
image_repository="kubectl-shovel/dumper-integration-tests"
echo "Building dumper binary ($cluster_os/$cluster_arch):"
GOOS=$cluster_os GOARCH=$cluster_arch CGO_ENABLED=0 \
go build \
-v \
-o "$dumper_binary" \
"./dumper"

echo "Building dumper's image..."
echo "Building dumper docker image ($cluster_os/$cluster_arch):"
docker buildx build \
--platform "linux/$arch" \
--platform "$cluster_os/$cluster_arch" \
--progress plain \
--load \
-t ${image_repository}:${image_tag} \
-f "${project_dir}/dumper/Dockerfile" \
"${project_dir}/dumper"
rm "${project_dir}/dumper/bin/dumper"
-t "$dumper_image_repository:$dumper_image_tag" \
-f "$dumper_context/Dockerfile" \
"$dumper_context"
rm "$dumper_binary"

echo "Building sample docker image ($cluster_os/$cluster_arch):"
docker buildx build \
--platform "$cluster_os/$cluster_arch" \
--progress plain \
--load \
--build-arg SDK_IMAGE_TAG="$sample_sdk_image_tag" \
--build-arg RUNTIME_IMAGE_TAG="$sample_runtime_image_tag" \
--build-arg FRAMEWORK="$framework" \
-t "$sample_image_repository:$sample_image_tag" \
-t "$sample_image_repository:latest" \
-f "$sample_context/Dockerfile" \
"$sample_context"

images=(
"$dumper_image_repository:$dumper_image_tag"
"$sample_image_repository:$sample_image_tag"
"$sample_image_repository:latest"
)

for image in "${images[@]}"; do
echo "Loading image to cluster ($image):"
kind load docker-image "$image"
done

echo "Loading dumper's image to kind cluster..."
kind load docker-image ${image_repository}:${image_tag}
8 changes: 0 additions & 8 deletions hacks/run-doc-generation.sh

This file was deleted.

33 changes: 20 additions & 13 deletions hacks/run-integration-tests.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
#!/bin/bash
set -ox errexit
#!/usr/bin/env bash
set -eu

[ $# -lt 1 ] && echo "Usage: $(basename $0) <framework|[netcoreapp3.1, net5.0, net6.0]>" && exit 1

framework=$1

if [ "$framework" != "netcoreapp3.1" ] && [ "$framework" == "net5.0" ] && [ "$framework" == "net6.0" ]; then
echo "Unsupported .net target framework $framework specified, choose from: netcoreapp3.1, net5.0 or net6.0"
exit 1
fi

ci=${CI:-""}
flags=""

if [ -z "$ci" ]; then
if [ -z "${CI:-""}" ]; then
# disable parallel execution on locally for better debugging experience
flags="-parallel 1"
else
flags="-parallel 4"
# restrict parallel degree for ci mode because kind can hang with large amount of pods
flags="-parallel 2"
fi

echo "Running tests..."
go test -v $flags \
go test $flags \
-v \
-ldflags="-X github.com/dodopizza/kubectl-shovel/test/integration_test.TargetContainerImage=kubectl-shovel/sample-integration-tests:$framework" \
-timeout 600s \
--tags=integration \
-timeout 300s \
./test/integration/... |
sed "/PASS/s//$(printf "\033[32mPASS\033[0m")/" |
sed "/FAIL/s//$(printf "\033[31mFAIL\033[0m")/"

exit ${PIPESTATUS[0]}
./test/integration/...
17 changes: 9 additions & 8 deletions hacks/run-unit-tests.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#!/bin/bash
#!/usr/bin/env bash
set -eu

if [[ -z ${TEST_DIR} ]]
then
TEST_DIR="./..."
fi

go test ${TEST_RUN_ARGS} -v -race -cover \
-coverprofile cover.out ${TEST_DIR} -timeout 30s | \
go test \
-v \
-race \
-cover \
-coverprofile cover.out \
-timeout 30s \
./... |
sed "/PASS/s//$(printf "\033[32mPASS\033[0m")/" | \
sed "/FAIL/s//$(printf "\033[31mFAIL\033[0m")/"

Expand Down
2 changes: 1 addition & 1 deletion internal/events/event_awaiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (ep *EventAwaiter) parse() (string, error) {
case Status:
fmt.Println(event.Message)
case Error:
return "", fmt.Errorf("error in job occurred: %s", event.Message)
return "", fmt.Errorf("%s", event.Message)
case Completed:
return event.Message, nil
default:
Expand Down
26 changes: 13 additions & 13 deletions internal/kubernetes/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,32 @@ import (
)

// CopyFromPod - copy file from pod to local file
func (k8s *Client) CopyFromPod(podName, podFilePath, localFilePath string) error {
from := buildPodPath(k8s.Namespace, podName, podFilePath)
func (k *Client) CopyFromPod(podName, podFilePath, localFilePath string) error {
from := buildPodPath(k.Namespace, podName, podFilePath)

return k8s.copy(from, localFilePath)
return k.copy(from, localFilePath)
}

// CopyToPod - copy local file to pod
func (k8s *Client) CopyToPod(localFilePath, podName, podFilePath string) error {
to := buildPodPath(k8s.Namespace, podName, podFilePath)
func (k *Client) CopyToPod(localFilePath, podName, podFilePath string) error {
to := buildPodPath(k.Namespace, podName, podFilePath)

return k8s.copy(localFilePath, to)
return k.copy(localFilePath, to)
}

func buildPodPath(namespace, podName, podFilePath string) string {
return fmt.Sprintf("%s/%s:%s", namespace, podName, podFilePath)
}

func (k8s *Client) copy(from, to string) error {
func (k *Client) copy(from, to string) error {
ioStreams := genericclioptions.IOStreams{
In: &bytes.Buffer{},
Out: &bytes.Buffer{},
ErrOut: os.Stdout,
}
opts := cp.NewCopyOptions(ioStreams)
opts.Clientset = k8s.Clientset
opts.ClientConfig = k8s.Config
opts.Clientset = k.Clientset
opts.ClientConfig = k.Config

return opts.Run([]string{from, to})
}

func buildPodPath(namespace, podName, podFilePath string) string {
return fmt.Sprintf("%s/%s:%s", namespace, podName, podFilePath)
}
Loading

0 comments on commit fd12c59

Please sign in to comment.