mirror of https://github.com/knative/func.git
301 lines
8.4 KiB
Go
301 lines
8.4 KiB
Go
//go:build !integration
|
|
// +build !integration
|
|
|
|
package functions
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
fnlabels "knative.dev/func/pkg/k8s/labels"
|
|
|
|
. "knative.dev/func/pkg/testing"
|
|
)
|
|
|
|
// TestFunction_Validate ensures that we are permissive on what we accept and
|
|
// strict on what we emit. This takes the form of not validating a function
|
|
// on instantiation, but rather on write. A function is expected to be in a
|
|
// partial, even invalid state on disk; mostly due to the possibility of manual
|
|
// editing of the func.yaml. Writing, however, should always to write a
|
|
// function in a known valid state.
|
|
func TestFunction_Validate(t *testing.T) {
|
|
root, cleanup := Mktemp(t)
|
|
t.Cleanup(cleanup)
|
|
|
|
var f Function
|
|
var err error
|
|
|
|
// Loading a nonexistent (new) function should not fail
|
|
// I.e. it will not run .Validate, or it would error that the function at
|
|
// root has no language or name.
|
|
if f, err = NewFunction(root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Attempting to write the function will fail as being invalid
|
|
invalidEnv := "*invalid"
|
|
f.Build.BuildEnvs = []Env{{Name: &invalidEnv}}
|
|
if err = f.Write(); err == nil {
|
|
t.Fatalf("expected error writing an incomplete (invalid) function")
|
|
}
|
|
|
|
// Write the invalid Function
|
|
//
|
|
// Write this intentionally invalid function to disk.
|
|
// NOTE: this depends on an implementation detail of the package: the yaml
|
|
// serialization of the Function struct to a known filename. This is why this
|
|
// test belongs here in the same package as the implementation rather than in
|
|
// package functions_test which treats the function package as an opaque-box.
|
|
path := filepath.Join(root, FunctionFile)
|
|
bb, err := yaml.Marshal(&f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = os.WriteFile(path, bb, os.ModePerm); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Loading the invalid function should not fail, but validation should.
|
|
if f, err = NewFunction(root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = f.Validate(); err == nil { // axiom check; not strictly part of this test
|
|
t.Fatal("did not receive an error validating a known-invlaid (incomplete) function")
|
|
}
|
|
|
|
// Remove the invalid structures... write should complete without error.
|
|
f.Build.BuildEnvs = []Env{}
|
|
if err = f.Write(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if f, err = NewFunction(root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = f.Validate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestFunction_ImageWithDigest(t *testing.T) {
|
|
type fields struct {
|
|
Image string
|
|
ImageDigest string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want string
|
|
}{
|
|
{
|
|
name: "Full path with port",
|
|
fields: fields{Image: "image-registry.openshift-image-registry.svc.cluster.local:50000/default/bar", ImageDigest: "42"},
|
|
want: "image-registry.openshift-image-registry.svc.cluster.local:50000/default/bar@42",
|
|
},
|
|
{
|
|
name: "Path with namespace",
|
|
fields: fields{Image: "johndoe/bar", ImageDigest: "42"},
|
|
want: "johndoe/bar@42",
|
|
},
|
|
{
|
|
name: "Just image name",
|
|
fields: fields{Image: "bar:latest", ImageDigest: "42"},
|
|
want: "bar@42",
|
|
},
|
|
{
|
|
name: "Full path with port and SHA256 Digest",
|
|
fields: fields{Image: "image-registry.openshift-image-registry.svc.cluster.local:50000/default/bar@sha256:42", ImageDigest: "sha256:42"},
|
|
want: "image-registry.openshift-image-registry.svc.cluster.local:50000/default/bar@sha256:42",
|
|
},
|
|
{
|
|
name: "Full path with port and SHA256 Digest with empty ImageDigest",
|
|
fields: fields{Image: "image-registry.openshift-image-registry.svc.cluster.local:50000/default/bar@sha256:42", ImageDigest: ""},
|
|
want: "image-registry.openshift-image-registry.svc.cluster.local:50000/default/bar@sha256:42",
|
|
},
|
|
}
|
|
//TODO: gauron99 - this is gonna need to be changed (probably) because:
|
|
// 1: imageDigest now doesnt have a dedicated structure member (resolved?)
|
|
// 2: is still fetched after pushing the Function (which is a temporary fix -- it really should be during build)
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f := Function{
|
|
Build: BuildSpec{
|
|
Image: tt.fields.Image,
|
|
},
|
|
}
|
|
if got := f.ImageNameWithDigest(tt.fields.ImageDigest); got != tt.want {
|
|
t.Errorf("ImageNameWithDigest(tt.fields.ImageDigest) = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFunction_ImageName ensures that the full image name is
|
|
// returned for a Function, based on the Function's Registry and Name,
|
|
// including utilizing the DefaultRegistry if the Function's defined
|
|
// registry is a single token (just the namespace).
|
|
func TestFunction_ImageName(t *testing.T) {
|
|
var (
|
|
f Function
|
|
got string
|
|
err error
|
|
)
|
|
tests := []struct {
|
|
name string
|
|
registry string
|
|
funcName string
|
|
expectedImage string
|
|
expectError bool
|
|
}{
|
|
{"short-name", "alice", "myfunc", DefaultRegistry + "/alice/myfunc:latest", false},
|
|
{"short-name-trailing-slash", "alice/", "myfunc", DefaultRegistry + "/alice/myfunc:latest", false},
|
|
{"full-name-quay-io", "quay.io/alice", "myfunc", "quay.io/alice/myfunc:latest", false},
|
|
{"full-name-docker-io", "docker.io/alice", "myfunc", DefaultRegistry + "/alice/myfunc:latest", false},
|
|
{"full-name-with-sub-path", "docker.io/alice/sub", "myfunc", DefaultRegistry + "/alice/sub/myfunc:latest", false},
|
|
{"localhost-direct", "localhost:5000", "myfunc", "localhost:5000/myfunc:latest", false},
|
|
{"full-name-with-sub-sub-path", "us-central1-docker.pkg.dev/my-gcpproject/team/user", "myfunc", "us-central1-docker.pkg.dev/my-gcpproject/team/user/myfunc:latest", false},
|
|
{"missing-func-name", "alice", "", "", true},
|
|
{"missing-registry", "", "myfunc", "", true},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
f = Function{Registry: test.registry, Name: test.funcName}
|
|
got, err = f.ImageName()
|
|
if test.expectError && err == nil {
|
|
t.Errorf("registry '%v' and name '%v' did not yield the expected error",
|
|
test.registry, test.funcName)
|
|
}
|
|
if !test.expectError && err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if got != test.expectedImage {
|
|
t.Errorf("expected registry '%v' name '%v' to yield image '%v', got '%v'",
|
|
test.registry, test.funcName, test.expectedImage, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_LabelsMap(t *testing.T) {
|
|
key1 := "key1"
|
|
key2 := "key2"
|
|
value1 := "value1"
|
|
value2 := "value2"
|
|
|
|
t.Setenv("BAD_EXAMPLE", ":invalid")
|
|
valueLocalEnvIncorrect4 := "{{env:BAD_EXAMPLE}}"
|
|
|
|
t.Setenv("GOOD_EXAMPLE", "valid")
|
|
valueLocalEnv4 := "{{env:GOOD_EXAMPLE}}"
|
|
|
|
tests := []struct {
|
|
name string
|
|
labels []Label
|
|
expectErr bool
|
|
expectedMap map[string]string
|
|
}{
|
|
{
|
|
name: "invalid Labels should return err",
|
|
labels: []Label{
|
|
{
|
|
Value: &value1,
|
|
},
|
|
},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "with valid env var",
|
|
labels: []Label{
|
|
{
|
|
Key: &key1,
|
|
Value: &valueLocalEnv4,
|
|
},
|
|
},
|
|
expectErr: false,
|
|
expectedMap: map[string]string{
|
|
key1: "valid",
|
|
},
|
|
},
|
|
{
|
|
name: "with invalid env var",
|
|
labels: []Label{
|
|
{
|
|
Key: &key1,
|
|
Value: &valueLocalEnvIncorrect4,
|
|
},
|
|
},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "empty labels allowed. returns default labels",
|
|
labels: []Label{
|
|
{
|
|
Key: &key1,
|
|
},
|
|
},
|
|
expectErr: false,
|
|
expectedMap: map[string]string{
|
|
key1: "",
|
|
},
|
|
},
|
|
{
|
|
name: "full set of labels",
|
|
labels: []Label{
|
|
{
|
|
Key: &key1,
|
|
Value: &value1,
|
|
},
|
|
{
|
|
Key: &key2,
|
|
Value: &value2,
|
|
},
|
|
},
|
|
expectErr: false,
|
|
expectedMap: map[string]string{
|
|
key1: value1,
|
|
key2: value2,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f := Function{
|
|
Name: "some-function",
|
|
Runtime: "golang",
|
|
Deploy: DeploySpec{Labels: tt.labels},
|
|
}
|
|
got, err := f.LabelsMap()
|
|
|
|
if tt.expectErr {
|
|
if err == nil {
|
|
t.Error("expected error but didn't get an error from LabelsMap")
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("got unexpected err: %s", err)
|
|
}
|
|
}
|
|
if err == nil {
|
|
defaultLabels := expectedDefaultLabels(f)
|
|
for k, v := range defaultLabels {
|
|
tt.expectedMap[k] = v
|
|
}
|
|
if res := reflect.DeepEqual(got, tt.expectedMap); !res {
|
|
t.Errorf("mismatch in actual and expected labels return. actual: %#v, expected: %#v", got, tt.expectedMap)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func expectedDefaultLabels(f Function) map[string]string {
|
|
return map[string]string{
|
|
fnlabels.FunctionNameKey: f.Name,
|
|
fnlabels.FunctionRuntimeKey: f.Runtime,
|
|
}
|
|
}
|