Add test for "flux create source git"

The create source tests are more interesting than the existing tests as they
create objects then wit for the flux source reconciler to complete. The tests
simulate this with a background goroutine that waits for an object to be
created then uses a test specific function to update it.

The tests set a timeout so that if there is a failure they timeout somewhat
quickly rather than hanging for a longer period of time.

Signed-off-by: Allen Porter <allen@thebends.org>
pull/1696/head
Allen Porter 3 years ago
parent 83c7994266
commit 8cbd4e8172

@ -0,0 +1,131 @@
// +build unit
package main
import (
"context"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"testing"
"time"
)
var pollInterval = 50 * time.Millisecond
var testTimeout = 10 * time.Second
// Update the GitRepository once created to exercise test specific behavior
type reconcileFunc func(repo *sourcev1.GitRepository)
// reconciler waits for an object to be created, then invokes a test supplied
// function to mutate that object, simulating a controller.
// Test should invoke run() to run the background reconciler task which
// polls to wait for the object to exist before applying the update function.
// Any errors from the reconciler are asserted on test completion.
type reconciler struct {
client client.Client
name types.NamespacedName
reconcile reconcileFunc
}
// Start the background task that waits for the object to exist then applies
// the update function.
func (r *reconciler) run(t *testing.T) {
result := make(chan error)
go func() {
defer close(result)
err := wait.PollImmediate(
pollInterval,
testTimeout,
r.conditionFunc)
result <- err
}()
t.Cleanup(func() {
if err := <-result; err != nil {
t.Errorf("Failure from test reconciler: '%v':", err.Error())
}
})
}
// A ConditionFunction that waits for the named GitRepository to be created,
// then sets the ready condition to true.
func (r *reconciler) conditionFunc() (bool, error) {
var repo sourcev1.GitRepository
if err := r.client.Get(context.Background(), r.name, &repo); err != nil {
if errors.IsNotFound(err) {
return false, nil // Keep polling until object is created
}
return true, err
}
r.reconcile(&repo)
err := r.client.Status().Update(context.Background(), &repo)
return true, err
}
func TestCreateSourceGit(t *testing.T) {
// Default command used for multiple tests
var command = "create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch=master --timeout=" + testTimeout.String()
cases := []struct {
name string
args string
assert assertFunc
reconcile reconcileFunc
}{
{
"NoArgs",
"create source git",
assertError("GitRepository source name is required"),
nil,
}, {
"Succeeded",
command,
assertGoldenFile("testdata/create_source_git/success.golden"),
func(repo *sourcev1.GitRepository) {
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionTrue, sourcev1.GitOperationSucceedReason, "succeeded message")
repo.Status.Artifact = &sourcev1.Artifact{
Path: "some-path",
Revision: "v1",
}
},
}, {
"Failed",
command,
assertError("failed message"),
func(repo *sourcev1.GitRepository) {
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionFalse, sourcev1.URLInvalidReason, "failed message")
},
}, {
"NoArtifact",
command,
assertError("GitRepository source reconciliation completed but no artifact was found"),
func(repo *sourcev1.GitRepository) {
// Updated with no artifact
meta.SetResourceCondition(repo, meta.ReadyCondition, metav1.ConditionTrue, sourcev1.GitOperationSucceedReason, "succeeded message")
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ns := allocateNamespace("podinfo")
setupTestNamespace(ns, t)
if tc.reconcile != nil {
r := reconciler{
client: testEnv.client,
name: types.NamespacedName{Namespace: ns, Name: "podinfo"},
reconcile: tc.reconcile,
}
r.run(t)
}
cmd := cmdTestCase{
args: tc.args + " -n=" + ns,
assert: tc.assert,
}
cmd.runTestCmd(t)
})
}
}

@ -14,6 +14,7 @@ import (
"text/template"
"time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/google/go-cmp/cmp"
"github.com/mattn/go-shellwords"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -132,7 +133,9 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
tmpFilename := filepath.Join("/tmp", "kubeconfig-"+time.Nanosecond.String())
os.WriteFile(tmpFilename, kubeConfig, 0644)
k8sClient, err := client.NewWithWatch(cfg, client.Options{})
k8sClient, err := client.NewWithWatch(cfg, client.Options{
Scheme: utils.NewScheme(),
})
if err != nil {
return nil, err
}
@ -158,7 +161,9 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
if err != nil {
return nil, err
}
k8sClient, err := client.NewWithWatch(cfg, client.Options{})
k8sClient, err := client.NewWithWatch(cfg, client.Options{
Scheme: utils.NewScheme(),
})
if err != nil {
return nil, err
}

@ -3,7 +3,10 @@
package main
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"os"
"testing"
)
@ -32,3 +35,14 @@ func TestMain(m *testing.M) {
os.Exit(code)
}
func setupTestNamespace(namespace string, t *testing.T) {
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
err := testEnv.client.Create(context.Background(), ns)
if err != nil {
t.Fatalf("Failed to create namespace: %v", err)
}
t.Cleanup(func() {
_ = testEnv.client.Delete(context.Background(), ns)
})
}

@ -0,0 +1,6 @@
✚ generating GitRepository source
► applying GitRepository source
✔ GitRepository source created
◎ waiting for GitRepository source reconciliation
✔ GitRepository source reconciliation completed
✔ fetched revision: v1
Loading…
Cancel
Save