Skip to content
This repository has been archived by the owner on Oct 27, 2022. It is now read-only.

Add label filtering to CTS #28

Merged
merged 12 commits into from
Jul 4, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions config/crds/testing_v1alpha1_clustertestsuite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ spec:
description: Decide which tests to execute. If not provided execute
all tests
properties:
matchLabels:
description: Find test definitions by it's labels. TestDefinition
should have AT LEAST one label listed here to be executed.
matchLabelExpressions:
description: 'Find test definitions by their labels. TestDefinition
sjanota marked this conversation as resolved.
Show resolved Hide resolved
must match AT LEAST one expression listed here to be executed.
For the complete grammar see: https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go#L811'
items:
type: string
type: array
Expand Down
14 changes: 14 additions & 0 deletions config/samples/advanced/testsuite-select-by-labels.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: testing.kyma-project.io/v1alpha1
kind: ClusterTestSuite
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: testsuite-selected-by-labels
spec:
count: 1
selectors:
matchLabelExpressions:
sjanota marked this conversation as resolved.
Show resolved Hide resolved
- "test-duration!=long" # do not execute long running tests
sjanota marked this conversation as resolved.
Show resolved Hide resolved
- "component in (frontend, backend)" # execute all tests for frontend and backend, regardless of their execution time.

7 changes: 4 additions & 3 deletions pkg/apis/testing/v1alpha1/testsuite_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,10 @@ type TestSuiteSpec struct {
type TestsSelector struct {
// Find test definitions by it's name
MatchNames []TestDefReference `json:"matchNames,omitempty"`
// Find test definitions by it's labels.
// TestDefinition should have AT LEAST one label listed here to be executed.
MatchLabels []string `json:"matchLabels,omitempty"`
// Find test definitions by their labels.
// TestDefinition must match AT LEAST one expression listed here to be executed.
// For the complete grammar see: https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go#L811
MatchLabelExpressions []string `json:"matchLabelExpressions,omitempty"`
}

type TestDefReference struct {
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/testing/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pkg/controller/testsuite/testsuite_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,8 @@ func (r *mockPodReconciler) getLogger() logr.Logger {

func startMockPodController(mgr manager.Manager, enforceConcurrentlyRunningPods int) (*mockPodReconciler, error) {
pr := &mockPodReconciler{
cli: mgr.GetClient(),
mtx: sync.Mutex{},
cli: mgr.GetClient(),
mtx: sync.Mutex{},
enforceNumberOfConcurrentlyRunningPods: enforceConcurrentlyRunningPods,
reconcileAfter: time.Millisecond * 100,
}
Expand Down
55 changes: 47 additions & 8 deletions pkg/fetcher/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"github.com/kyma-incubator/octopus/pkg/humanerr"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"

"github.com/kyma-incubator/octopus/pkg/apis/testing/v1alpha1"
Expand All @@ -22,33 +23,71 @@ type Definition struct {
reader client.Reader
}

type uniqueTestDefinitions map[types.UID]v1alpha1.TestDefinition

func (s *Definition) FindMatching(suite v1alpha1.ClusterTestSuite) ([]v1alpha1.TestDefinition, error) {
sjanota marked this conversation as resolved.
Show resolved Hide resolved
ctx := context.TODO()
if len(suite.Spec.Selectors.MatchNames) > 0 {
return s.findByNames(ctx, suite)
acc := make(uniqueTestDefinitions)

err := s.findByNames(ctx, suite, acc)
sjanota marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
// TODO(aszecowka) later so far we return all test definitions for all namespaces (https://github.com/kyma-incubator/octopus/issues/7)

err = s.findByLabelExpressions(ctx, suite, acc)
sjanota marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

if len(acc) > 0 {
sjanota marked this conversation as resolved.
Show resolved Hide resolved
return acc.getValues(), nil
}

var list v1alpha1.TestDefinitionList
if err := s.reader.List(ctx, &client.ListOptions{Namespace: ""}, &list); err != nil {
return nil, errors.Wrap(err, "while listing test definitions")
}
return list.Items, nil
}

func (s *Definition) findByNames(ctx context.Context, suite v1alpha1.ClusterTestSuite) ([]v1alpha1.TestDefinition, error) {
var list []v1alpha1.TestDefinition
func (s *Definition) findByNames(ctx context.Context, suite v1alpha1.ClusterTestSuite, acc uniqueTestDefinitions) error {
sjanota marked this conversation as resolved.
Show resolved Hide resolved
for _, tRef := range suite.Spec.Selectors.MatchNames {
def := v1alpha1.TestDefinition{}
err := s.reader.Get(ctx, types.NamespacedName{Name: tRef.Name, Namespace: tRef.Namespace}, &def)
wrappedErr := errors.Wrapf(err, "while fetching test definition from selector [name: %s, namespace: %s]", tRef.Name, tRef.Namespace)
switch {
case err == nil:
case k8serrors.IsNotFound(err):
return nil, humanerr.NewError(wrappedErr, fmt.Sprintf("Test Definition [name: %s, namespace: %s] does not exist", tRef.Name, tRef.Namespace))
return humanerr.NewError(wrappedErr, fmt.Sprintf("Test Definition [name: %s, namespace: %s] does not exist", tRef.Name, tRef.Namespace))
default:
return nil, humanerr.NewError(wrappedErr, "Internal error")
return humanerr.NewError(wrappedErr, "Internal error")
}
acc[def.UID] = def
}
return nil
}

func (s *Definition) findByLabelExpressions(ctx context.Context, suite v1alpha1.ClusterTestSuite, acc uniqueTestDefinitions) error {
for _, expr := range suite.Spec.Selectors.MatchLabelExpressions {
selector, err := labels.Parse(expr)
if err != nil {
return errors.Wrapf(err, "while parsing label expression [expression: %s]", expr)
}
var list v1alpha1.TestDefinitionList
if err := s.reader.List(ctx, &client.ListOptions{LabelSelector: selector}, &list); err != nil {
sjanota marked this conversation as resolved.
Show resolved Hide resolved
return errors.Wrapf(err, "while fetching test definition from selector [expression: %s]", expr)
}
for _, def := range list.Items {
acc[def.UID] = def
}
}
return nil
}

func (m uniqueTestDefinitions) getValues() []v1alpha1.TestDefinition {
var list []v1alpha1.TestDefinition
for _, def := range m {
list = append(list, def)
}
return list, nil
return list
}
150 changes: 149 additions & 1 deletion pkg/fetcher/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"github.com/kyma-incubator/octopus/pkg/humanerr"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/uuid"
sjanota marked this conversation as resolved.
Show resolved Hide resolved
"sigs.k8s.io/controller-runtime/pkg/client"
"testing"

Expand Down Expand Up @@ -41,18 +42,21 @@ func TestFindMatching(t *testing.T) {
// GIVEN
testA := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-a",
Namespace: "test-a",
},
}
testB := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-b",
Namespace: "test-b",
},
}
testC := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-c",
Namespace: "test-c",
},
Expand Down Expand Up @@ -87,6 +91,133 @@ func TestFindMatching(t *testing.T) {

})

t.Run("return tests selected by label expressions", func(t *testing.T) {
sjanota marked this conversation as resolved.
Show resolved Hide resolved
sjanota marked this conversation as resolved.
Show resolved Hide resolved
// GIVEN
testA := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-a",
Namespace: "test-a",
Labels: map[string]string{
"test": "true",
},
},
}
testB := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-b",
Namespace: "test-b",
Labels: map[string]string{
"test": "false",
},
},
}
testC := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-c",
Namespace: "test-c",
Labels: map[string]string{
"other": "123",
},
},
}

fakeCli := fake.NewFakeClientWithScheme(sch,
testA, testB, testC,
)
mockReader := &mockListReader{
fakeCli: fakeCli,
listResults: [][]v1alpha1.TestDefinition{
{*testC},
{*testA},
},
}
service := fetcher.NewForDefinition(mockReader)
// WHEN
out, err := service.FindMatching(v1alpha1.ClusterTestSuite{
Spec: v1alpha1.TestSuiteSpec{
Selectors: v1alpha1.TestsSelector{
MatchLabelExpressions: []string{
"other",
"test=true",
},
},
},
})
// THEN
require.NoError(t, err)
assert.Len(t, out, 2)
assert.Contains(t, out, *testA)
assert.Contains(t, out, *testC)
})

t.Run("return tests returns unique results", func(t *testing.T) {
sjanota marked this conversation as resolved.
Show resolved Hide resolved
// GIVEN
testA := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-a",
Namespace: "test-a",
Labels: map[string]string{
"test": "true",
},
},
}
testB := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-b",
Namespace: "test-b",
Labels: map[string]string{
"test": "false",
},
},
}
testC := &v1alpha1.TestDefinition{
ObjectMeta: v1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "test-c",
Namespace: "test-c",
Labels: map[string]string{
"other": "123",
},
},
}

fakeCli := fake.NewFakeClientWithScheme(sch,
testA, testB, testC,
)
mockReader := &mockListReader{
fakeCli: fakeCli,
listResults: [][]v1alpha1.TestDefinition{
{*testA},
},
}
service := fetcher.NewForDefinition(mockReader)
// WHEN
out, err := service.FindMatching(v1alpha1.ClusterTestSuite{
Spec: v1alpha1.TestSuiteSpec{
Selectors: v1alpha1.TestsSelector{
MatchNames: []v1alpha1.TestDefReference{
{
Name: "test-a",
Namespace: "test-a",
},
},
MatchLabelExpressions: []string{
"test=true",
},
},
},
})
// THEN
require.NoError(t, err)
assert.Len(t, out, 1)
assert.Contains(t, out, *testA)
})

t.Run("return error if test selected by name does not exist", func(t *testing.T) {
// GIVEN
fakeCli := fake.NewFakeClientWithScheme(sch)
Expand All @@ -113,7 +244,7 @@ func TestFindMatching(t *testing.T) {

t.Run("return internal error when fetching selected tests failed", func(t *testing.T) {
// GIVEN
errClient := &mockErrReader{err:errors.New("some error")}
errClient := &mockErrReader{err: errors.New("some error")}
service := fetcher.NewForDefinition(errClient)

// WHEN
Expand Down Expand Up @@ -150,3 +281,20 @@ func (m *mockErrReader) Get(ctx context.Context, key client.ObjectKey, obj runti
func (m *mockErrReader) List(ctx context.Context, opts *client.ListOptions, list runtime.Object) error {
return m.err
}

type mockListReader struct {
sjanota marked this conversation as resolved.
Show resolved Hide resolved
sjanota marked this conversation as resolved.
Show resolved Hide resolved
fakeCli client.Reader
listResults [][]v1alpha1.TestDefinition
calls uint
}

func (m *mockListReader) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
return m.fakeCli.Get(ctx, key, obj)
}

func (m *mockListReader) List(ctx context.Context, opts *client.ListOptions, list runtime.Object) error {
result := m.listResults[m.calls]
m.calls++
list.(*v1alpha1.TestDefinitionList).Items = append(list.(*v1alpha1.TestDefinitionList).Items, result...)
return nil
}