From 18a119abffbb411a4f43eae4e4c3b2620a9bac8b Mon Sep 17 00:00:00 2001 From: Luke Kingland Date: Thu, 3 Jul 2025 09:33:54 +0900 Subject: [PATCH] integration test isolation (#2894) - Default builder and pusher set to embedded Host Builder/Pusher(oci) - Most tests clear environment - Environment defaults can be controlled via environment variables - Tests which require back-compat `git` binary actively check and skip when running with a cleared environment (both integration and unit). - Bugfixes for when run in tandem with E2E tests - Ignores go-created directories in the default home path (testdata) --- .gitignore | 17 +- pkg/functions/client_int_test.go | 319 +++++++++++------- pkg/functions/client_test.go | 9 +- pkg/functions/function_test.go | 5 +- pkg/functions/repositories_test.go | 41 +++ pkg/functions/repository_test.go | 2 + pkg/functions/templates_test.go | 14 + pkg/functions/testdata/default_home/README.md | 3 + pkg/oci/go_builder.go | 4 +- 9 files changed, 279 insertions(+), 135 deletions(-) create mode 100644 pkg/functions/testdata/default_home/README.md diff --git a/.gitignore b/.gitignore index 7ca26279f..8afeced7d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,10 +10,13 @@ /hack/bin /.artifacts -/e2e/testdata/default_home/go -/e2e/testdata/default_home/.cache - /pkg/functions/testdata/migrations/*/.gitignore +/pkg/functions/testdata/default_home/go +/pkg/functions/testdata/default_home/.cache +/pkg/functions/testdata/migrations/*/.gitignore + +# Go +/templates/go/cloudevents/go.sum # JS node_modules @@ -25,10 +28,12 @@ __pycache__ /templates/python/cloudevents/.venv /templates/python/http/.venv -# VSCode +# E2E Tests +/e2e/testdata/default_home/go +/e2e/testdata/default_home/.cache + +# Editors .vscode - -# IntelliJ .idea # Operating system temporary files diff --git a/pkg/functions/client_int_test.go b/pkg/functions/client_int_test.go index 464303657..682975c35 100644 --- a/pkg/functions/client_int_test.go +++ b/pkg/functions/client_int_test.go @@ -9,8 +9,9 @@ import ( "io" "net/http" "os" + "os/exec" "path/filepath" - "reflect" + "strconv" "testing" "time" @@ -18,11 +19,11 @@ import ( "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" - "knative.dev/func/pkg/builders/buildpacks" "knative.dev/func/pkg/builders/s2i" "knative.dev/func/pkg/docker" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/knative" + "knative.dev/func/pkg/oci" . "knative.dev/func/pkg/testing" "knative.dev/pkg/ptr" ) @@ -31,57 +32,60 @@ import ( // // go test -tags integration ./... // -// ## Cluster Required +// ## Requirements // -// These integration tests require a properly configured cluster, -// such as that which is setup and configured in CI (see .github/workflows). -// Linux developers can set up the cluster via: +// A cluster is required. See .github/workflows for more. For example: // // ./hack/install-binaries.sh && ./hack/allocate.sh && ./hack/registry.sh // +// Binaries are required: go for compiling functions and git for +// repository-related tests. +// // ## Cluster Cleanup // // The test cluster and most resources can be removed with: // ./hack/delete.sh // +// ## Configuration +// +// Use the FUNC_INT_* environment variables to alter behavior, binaries to +// use, etc. +// // NOTE: Downloaded images are not removed. // const ( - // DefaultRegistry must contain both the registry host and - // registry namespace at this time. This will likely be - // split and defaulted to the forthcoming in-cluster registry. - DefaultRegistry = "localhost:50000/func" - - // DefaultNamespace for the underlying deployments. Must be the same - // as is set up and configured (see hack/configure.sh) - DefaultNamespace = "func" + DefaultIntTestHome = "./testdata/default_home" + DefaultIntTestKubeconfig = "../../hack/bin/kubeconfig.yaml" + DefaultIntTestRegistry = "localhost:50000/func" + DefaultIntTestNamespace = "default" + DefaultIntTestVerbose = false ) -func TestList(t *testing.T) { - verbose := true +var ( + Go = getEnvAsBin("FUNC_INT_GO", "go") + Git = getEnvAsBin("FUNC_INT_GIT", "git") + Kubeconfig = getEnvAsPath("FUNC_INT_KUBECONFIG", DefaultIntTestKubeconfig) + Verbose = getEnvAsBool("FUNC_INT_VERBOSE", DefaultIntTestVerbose) + Registry = getEnv("FUNC_INT_REGISTRY", DefaultIntTestRegistry) + Home, _ = filepath.Abs(DefaultIntTestHome) +) - // Assemble - lister := knative.NewLister(verbose) - - client := fn.New( - fn.WithLister(lister), - fn.WithVerbose(verbose)) - - // Act - names, err := client.List(context.Background(), DefaultNamespace) - if err != nil { - t.Fatal(err) - } - - // Assert - if len(names) != 0 { - t.Fatalf("Expected no functions, got %v", names) +// containsInstance checks if the list includes the given instance. +func containsInstance(list []fn.ListItem, name, namespace string) bool { + for _, v := range list { + if v.Name == name && v.Namespace == namespace { + return true + } } + return false + // Note that client.List is tested implicitly via its use in TestInt_New + // and TestInt_Delete. } -// TestNew creates -func TestNew(t *testing.T) { +// TestInt_New creates +func TestInt_New(t *testing.T) { + resetEnv() // Assemble root, cleanup := Mktemp(t) defer cleanup() @@ -90,32 +94,32 @@ func TestNew(t *testing.T) { client := newClient(verbose) // Act - if _, _, err := client.New(context.Background(), fn.Function{Name: name, Namespace: DefaultNamespace, Root: root, Runtime: "go"}); err != nil { + if _, _, err := client.New(context.Background(), fn.Function{Name: name, Namespace: DefaultIntTestNamespace, Root: root, Runtime: "go"}); err != nil { t.Fatal(err) } - defer del(t, client, name, DefaultNamespace) + defer del(t, client, name, DefaultIntTestNamespace) // Assert - items, err := client.List(context.Background(), DefaultNamespace) - names := []string{} - for _, item := range items { - names = append(names, item.Name) - } + list, err := client.List(context.Background(), DefaultIntTestNamespace) if err != nil { t.Fatal(err) } - if !reflect.DeepEqual(names, []string{name}) { - t.Fatalf("Expected function list ['%v'], got %v", name, names) + + if !containsInstance(list, name, DefaultIntTestNamespace) { + t.Log(list) + t.Fatalf("deployed instance list does not contain function %q", name) } } -// TestDeploy_Defaults deployes using client methods from New but manually -func TestDeploy_Defaults(t *testing.T) { - defer Within(t, "testdata/example.com/deploy")() +// TestInt_Deploy_Defaults deployes using client methods from New but manually +func TestInt_Deploy_Defaults(t *testing.T) { + resetEnv() + _, cleanup := Mktemp(t) + defer cleanup() verbose := true client := newClient(verbose) - f := fn.Function{Name: "deploy", Namespace: DefaultNamespace, Root: ".", Runtime: "go"} + f := fn.Function{Name: "deploy", Namespace: DefaultIntTestNamespace, Runtime: "go"} var err error if f, err = client.Init(f); err != nil { @@ -128,7 +132,7 @@ func TestDeploy_Defaults(t *testing.T) { t.Fatal(err) } - defer del(t, client, "deploy", DefaultNamespace) + defer del(t, client, "deploy", DefaultIntTestNamespace) // TODO: gauron99 -- remove this when you set full image name after build instead // of push -- this has to be here because of a workaround f.Deploy.Image = f.Build.Image @@ -138,13 +142,14 @@ func TestDeploy_Defaults(t *testing.T) { } } -// TestDeploy_WithOptions deploys function with all options explicitly set -func TestDeploy_WithOptions(t *testing.T) { +// TestInt_Deploy_WithOptions deploys function with all options explicitly set +func TestInt_Deploy_WithOptions(t *testing.T) { + resetEnv() root, cleanup := Mktemp(t) defer cleanup() verbose := false - f := fn.Function{Runtime: "go", Name: "test-deploy-with-options", Root: root, Namespace: DefaultNamespace} + f := fn.Function{Runtime: "go", Name: "test-deploy-with-options", Root: root, Namespace: DefaultIntTestNamespace} f.Deploy = fn.DeploySpec{ Options: fn.Options{ Scale: &fn.ScaleOptions{ @@ -172,15 +177,16 @@ func TestDeploy_WithOptions(t *testing.T) { if _, _, err := client.New(context.Background(), f); err != nil { t.Fatal(err) } - defer del(t, client, "test-deploy-with-options", DefaultNamespace) + defer del(t, client, "test-deploy-with-options", DefaultIntTestNamespace) } -func TestDeployWithTriggers(t *testing.T) { +func TestInt_Deploy_WithTriggers(t *testing.T) { + resetEnv() root, cleanup := Mktemp(t) defer cleanup() verbose := true - f := fn.Function{Runtime: "go", Name: "test-deploy-with-triggers", Root: root, Namespace: DefaultNamespace} + f := fn.Function{Runtime: "go", Name: "test-deploy-with-triggers", Root: root, Namespace: DefaultIntTestNamespace} f.Deploy = fn.DeploySpec{ Subscriptions: []fn.KnativeSubscription{ { @@ -197,27 +203,29 @@ func TestDeployWithTriggers(t *testing.T) { if _, _, err := client.New(context.Background(), f); err != nil { t.Fatal(err) } - defer del(t, client, "test-deploy-with-triggers", DefaultNamespace) + defer del(t, client, "test-deploy-with-triggers", DefaultIntTestNamespace) } -func TestUpdateWithAnnotationsAndLabels(t *testing.T) { +func TestInt_Update_WithAnnotationsAndLabels(t *testing.T) { + resetEnv() + _, cleanup := Mktemp(t) + defer cleanup() functionName := "updateannlab" - defer Within(t, "testdata/example.com/"+functionName)() verbose := false - servingClient, err := knative.NewServingClient(DefaultNamespace) + servingClient, err := knative.NewServingClient(DefaultIntTestNamespace) if err != nil { t.Fatal(err) } // Deploy a function without any annotations or labels client := newClient(verbose) - f := fn.Function{Name: functionName, Root: ".", Runtime: "go", Namespace: DefaultNamespace} + f := fn.Function{Name: functionName, Runtime: "go", Namespace: DefaultIntTestNamespace} if _, f, err = client.New(context.Background(), f); err != nil { t.Fatal(err) } - defer del(t, client, functionName, DefaultNamespace) + defer del(t, client, functionName, DefaultIntTestNamespace) // Updated function with a new set of annotations and labels // deploy and check that deployed kcsv contains correct annotations and labels @@ -295,41 +303,47 @@ func TestUpdateWithAnnotationsAndLabels(t *testing.T) { } } -// TestRemove ensures removal of a function instance. -func TestRemove(t *testing.T) { - defer Within(t, "testdata/example.com/remove")() +// TestInt_Remove ensures removal of a function instance. +func TestInt_Remove(t *testing.T) { + resetEnv() + _, cleanup := Mktemp(t) + defer cleanup() verbose := true + name := "remove" client := newClient(verbose) - f := fn.Function{Name: "remove", Namespace: DefaultNamespace, Root: ".", Runtime: "go"} + f := fn.Function{Name: name, Namespace: DefaultIntTestNamespace, Runtime: "go"} var err error if _, _, err = client.New(context.Background(), f); err != nil { t.Fatal(err) } - del(t, client, "remove", DefaultNamespace) + del(t, client, "remove", DefaultIntTestNamespace) - names, err := client.List(context.Background(), DefaultNamespace) + list, err := client.List(context.Background(), DefaultIntTestNamespace) if err != nil { t.Fatal(err) } - if len(names) != 0 { - t.Fatalf("Expected empty functions list, got %v", names) + if containsInstance(list, name, DefaultIntTestNamespace) { + t.Log(list) + t.Fatalf("deployed instance list still contains function %q", name) } } -// TestRemoteRepositories ensures that initializing a function +// TestInt_RemoteRepositories ensures that initializing a function // defined in a remote repository finds the template, writes // the expected files, and retains the expected modes. // NOTE: this test only succeeds due to an override in // templates' copyNode which forces mode 755 for directories. // See https://github.com/go-git/go-git/issues/364 -func TestRemoteRepositories(t *testing.T) { - defer Within(t, "testdata/example.com/remote")() +func TestInt_RemoteRepositories(t *testing.T) { + resetEnv() + _, cleanup := Mktemp(t) + defer cleanup() // Write the test template from the remote onto root client := fn.New( - fn.WithRegistry(DefaultRegistry), + fn.WithRegistry(DefaultIntTestRegistry), fn.WithRepository("https://github.com/boson-project/test-templates"), ) _, err := client.Init(fn.Function{ @@ -368,22 +382,23 @@ func TestRemoteRepositories(t *testing.T) { } } -// TestInvoke_ClientToService ensures that the client can invoke a remotely +// TestInt_Invoke_ClientToService ensures that the client can invoke a remotely // deployed service, both by the route returned directly as well as using // the invocation helper client.Invoke. -func TestInvoke_ClientToService(t *testing.T) { +func TestInt_Invoke_ClientToService(t *testing.T) { + resetEnv() + root, cleanup := Mktemp(t) + defer cleanup() var ( - root, done = Mktemp(t) - verbose = true - ctx = context.Background() - client = newClient(verbose) - route string - err error + verbose = true + ctx = context.Background() + client = newClient(verbose) + route string + err error ) - defer done() // Create a function - f := fn.Function{Name: "f", Runtime: "go", Namespace: DefaultNamespace} + f := fn.Function{Name: "f", Runtime: "go", Namespace: DefaultIntTestNamespace} f, err = client.Init(f) if err != nil { t.Fatal(err) @@ -391,12 +406,9 @@ func TestInvoke_ClientToService(t *testing.T) { source := ` package function -import ( - "context" - "net/http" -) +import "net/http" -func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) { +func Handle(res http.ResponseWriter, req *http.Request) { res.Write([]byte("TestInvoke_ClientToService OK")) } ` @@ -411,7 +423,7 @@ func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) { if err := f.Write(); err != nil { t.Fatal(err) } - defer del(t, client, "f", DefaultNamespace) + defer del(t, client, "f", DefaultIntTestNamespace) // Invoke via the route resp, err := http.Get(route) @@ -439,9 +451,10 @@ func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) { } } -// TestInvoke_ServiceToService ensures that a Function can invoke another +// TestInt_Invoke_ServiceToService ensures that a Function can invoke another // service via localhost service discovery api provided by the Dapr sidecar. -func TestInvoke_ServiceToService(t *testing.T) { +func TestInt_Invoke_ServiceToService(t *testing.T) { + resetEnv() var ( verbose = true ctx = context.Background() @@ -455,7 +468,7 @@ func TestInvoke_ServiceToService(t *testing.T) { // A function which responds to GET requests with a static value. root, done := Mktemp(t) defer done() - f := fn.Function{Name: "a", Runtime: "go", Namespace: DefaultNamespace} + f := fn.Function{Name: "a", Runtime: "go", Namespace: DefaultIntTestNamespace} f, err = client.Init(f) if err != nil { t.Fatal(err) @@ -463,12 +476,9 @@ func TestInvoke_ServiceToService(t *testing.T) { source = ` package function -import ( - "context" - "net/http" -) +import "net/http" -func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) { +func Handle(res http.ResponseWriter, req *http.Request) { res.Write([]byte("TestInvoke_ServiceToService OK")) } ` @@ -479,15 +489,16 @@ func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) { if _, _, err = client.Apply(ctx, f); err != nil { t.Fatal(err) } - defer del(t, client, "a", DefaultNamespace) + defer del(t, client, "a", DefaultIntTestNamespace) // Create Function B // which responds with the response from an invocation of 'a' via the // localhost service discovery and invocation API. + client2 := newClient(verbose) root, done = Mktemp(t) defer done() - f = fn.Function{Name: "b", Runtime: "go", Namespace: DefaultNamespace} - f, err = client.Init(f) + f = fn.Function{Name: "b", Runtime: "go", Namespace: DefaultIntTestNamespace} + f, err = client2.Init(f) if err != nil { t.Fatal(err) } @@ -497,7 +508,6 @@ package function import ( "bytes" - "context" "fmt" "io" "net/http" @@ -505,7 +515,7 @@ import ( "time" ) -func Handle(ctx context.Context, w http.ResponseWriter, req *http.Request) { +func Handle(w http.ResponseWriter, req *http.Request) { var e error var r *http.Response var buff bytes.Buffer @@ -535,10 +545,10 @@ func Handle(ctx context.Context, w http.ResponseWriter, req *http.Request) { if err != nil { t.Fatal(err) } - if route, _, err = client.Apply(ctx, f); err != nil { + if route, f, err = client2.Apply(ctx, f); err != nil { t.Fatal(err) } - defer del(t, client, "b", DefaultNamespace) + defer client2.Remove(ctx, "", "", f, true) resp, err := http.Get(route) if err != nil { @@ -566,7 +576,7 @@ func TestDeployWithoutHome(t *testing.T) { verbose := false name := "test-deploy-no-home" - f := fn.Function{Runtime: "node", Name: name, Root: root, Namespace: DefaultNamespace} + f := fn.Function{Runtime: "node", Name: name, Root: root, Namespace: DefaultIntTestNamespace} // client with s2i builder because pack needs HOME client := newClientWithS2i(verbose) @@ -577,32 +587,84 @@ func TestDeployWithoutHome(t *testing.T) { t.Fatalf("expected no errors but got %v", err) } - defer del(t, client, name, DefaultNamespace) + defer del(t, client, name, DefaultIntTestNamespace) } // *********** -// Helpers +// Helpers // *********** +func getEnvAsPath(env, dflt string) (val string) { + val = getEnv(env, dflt) + if !filepath.IsAbs(val) { // convert to abs + var err error + if val, err = filepath.Abs(val); err != nil { + panic(fmt.Sprintf("error converting path to absolute. %v", err)) + } + } + return +} + +func getEnvAsBool(env string, dfltBool bool) bool { + dflt := fmt.Sprintf("%t", dfltBool) + val, err := strconv.ParseBool(getEnv(env, dflt)) + if err != nil { + panic(fmt.Sprintf("value for %v expected to be boolean. %v", env, err)) + } + return val +} + +func getEnvAsBin(env, dflt string) string { + val, err := exec.LookPath(getEnv(env, dflt)) + if err != nil { + fmt.Fprintf(os.Stderr, "error locating command %q. %v", val, err) + } + return val +} + +func getEnv(env, dflt string) (val string) { + if v := os.Getenv(env); v != "" { + val = v + } + if val == "" { + val = dflt + } + return +} + +func resetEnv() { + os.Clearenv() + os.Setenv("HOME", Home) + os.Setenv("KUBECONFIG", Kubeconfig) + os.Setenv("FUNC_GO", Go) + os.Setenv("FUNC_GIT", Git) + os.Setenv("FUNC_VERBOSE", fmt.Sprintf("%t", Verbose)) + + // The Registry will be set either during first-time setup using the + // global config, or already defaulted by the user via environment variable. + os.Setenv("FUNC_REGISTRY", Registry) + + // The following host-builder related settings will become the defaults + // once the host builder supports the core runtimes. Setting them here in + // order to futureproof individual tests. + os.Setenv("FUNC_ENABLE_HOST_BUILDER", "true") // Enable the host builder + os.Setenv("FUNC_BUILDER", "host") // default to host builder + os.Setenv("FUNC_CONTAINER", "false") // "run" uses host builder + +} + // newClient creates an instance of the func client with concrete impls // sufficient for running integration tests. func newClient(verbose bool) *fn.Client { - builder := buildpacks.NewBuilder(buildpacks.WithVerbose(verbose)) - pusher := docker.NewPusher(docker.WithVerbose(verbose)) - deployer := knative.NewDeployer(knative.WithDeployerVerbose(verbose)) - describer := knative.NewDescriber(verbose) - remover := knative.NewRemover(verbose) - lister := knative.NewLister(verbose) - return fn.New( - fn.WithRegistry(DefaultRegistry), + fn.WithRegistry(DefaultIntTestRegistry), + fn.WithBuilder(oci.NewBuilder("", verbose)), + fn.WithPusher(oci.NewPusher(true, true, verbose)), + fn.WithDeployer(knative.NewDeployer(knative.WithDeployerVerbose(verbose))), + fn.WithDescriber(knative.NewDescriber(verbose)), + fn.WithRemover(knative.NewRemover(verbose)), + fn.WithLister(knative.NewLister(verbose)), fn.WithVerbose(verbose), - fn.WithBuilder(builder), - fn.WithPusher(pusher), - fn.WithDeployer(deployer), - fn.WithDescriber(describer), - fn.WithRemover(remover), - fn.WithLister(lister), ) } @@ -616,7 +678,7 @@ func newClientWithS2i(verbose bool) *fn.Client { lister := knative.NewLister(verbose) return fn.New( - fn.WithRegistry(DefaultRegistry), + fn.WithRegistry(DefaultIntTestRegistry), fn.WithVerbose(verbose), fn.WithBuilder(builder), fn.WithPusher(pusher), @@ -640,10 +702,23 @@ func newClientWithS2i(verbose bool) *fn.Client { func del(t *testing.T, c *fn.Client, name, namespace string) { t.Helper() waitFor(t, c, name, namespace) - f := fn.Function{Name: name, Deploy: fn.DeploySpec{Namespace: DefaultNamespace}} + f := fn.Function{Name: name, Deploy: fn.DeploySpec{Namespace: DefaultIntTestNamespace}} if err := c.Remove(context.Background(), "", "", f, false); err != nil { t.Fatal(err) } + + // TODO(lkingland): The below breaks things + // The following should not exist, nor its imports. + // + // If there is ever a problem with dangling volumes, that is + // something which should be fixed in the _remover_, and tested there. + // + // ... check with Jefferson what caused such a state, open an + // issue to alter remover if necessary, and then delete all of this. + if true { + return + } + cli, _, err := docker.NewClient(client.DefaultDockerHost) if err != nil { t.Fatal(err) diff --git a/pkg/functions/client_test.go b/pkg/functions/client_test.go index a1bd274e7..e920f13a6 100644 --- a/pkg/functions/client_test.go +++ b/pkg/functions/client_test.go @@ -45,7 +45,7 @@ const ( // but functional deployer, use fn.WithDeployer(mock.NewDeployer()) which // will return a result with the target namespace populated "mocking" // that the function was actually deployed. - TestNamespace = "func" + TestNamespace = "default" ) var ( @@ -348,7 +348,9 @@ func TestClient_New_HiddenFilesIgnored(t *testing.T) { // $FUNC_REPOSITORIES_PATH/boson/go/json // See the CLI for full details, but a standard default location is // $HOME/.config/func/repositories/boson/go/json -func TestClient_New_RepositoriesExtensible(t *testing.T) { +func TestClient_New_RepositoriesExtensible_B(t *testing.T) { + skipIfNoGit(t) // see function doc + root := "testdata/example.com/test-repositories-extensible" defer Using(t, root)() @@ -391,6 +393,7 @@ func TestClient_New_RuntimeNotFoundError(t *testing.T) { // TestClient_New_RuntimeNotFoundCustom ensures that the correct error is returned // when the requested runtime is not found in a given custom repository func TestClient_New_RuntimeNotFoundCustom(t *testing.T) { + skipIfNoGit(t) // see docs root := "testdata/example.com/testRuntimeNotFoundCustom" defer Using(t, root)() @@ -429,6 +432,7 @@ func TestClient_New_TemplateNotFoundError(t *testing.T) { // TestClient_New_TemplateNotFoundCustom ensures that the correct error is returned // when the requested template is not found in the given custom repository. func TestClient_New_TemplateNotFoundCustom(t *testing.T) { + skipIfNoGit(t) // see docs root := "testdata/example.com/testTemplateNotFoundCustom" defer Using(t, root)() @@ -1611,6 +1615,7 @@ func TestClient_Scaffold(t *testing.T) { // TestClient_Runtimes ensures that the total set of runtimes are returned. func TestClient_Runtimes(t *testing.T) { + skipIfNoGit(t) // see docs // TODO: test when a specific repo override is indicated // (remote repo which takes precedence over embedded and extended) diff --git a/pkg/functions/function_test.go b/pkg/functions/function_test.go index f1c2ad9a8..943a4826f 100644 --- a/pkg/functions/function_test.go +++ b/pkg/functions/function_test.go @@ -448,8 +448,7 @@ func TestFunction_Local(t *testing.T) { // // The new function should not have Local.Remote set (as it is a transient field) func TestFunction_LocalTransient(t *testing.T) { - - // Initialise a new function + skipIfNoGit(t) // see docs root, rm := Mktemp(t) defer rm() @@ -520,7 +519,7 @@ func TestFunction_LocalTransient(t *testing.T) { InsecureSkipTLS: true, }) if err != nil { - t.Fatal() + t.Fatal(err) } // Create a new directory to clone the function in diff --git a/pkg/functions/repositories_test.go b/pkg/functions/repositories_test.go index 64cd7d3c6..14d24d4ba 100644 --- a/pkg/functions/repositories_test.go +++ b/pkg/functions/repositories_test.go @@ -5,6 +5,7 @@ package functions_test import ( "os" + "os/exec" "path/filepath" "testing" @@ -52,6 +53,7 @@ func TestRepositories_GetInvalid(t *testing.T) { // TestRepositories_Get ensures a repository can be accessed by name. func TestRepositories_Get(t *testing.T) { + skipIfNoGit(t) // see docs client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) // valid should not error @@ -69,6 +71,7 @@ func TestRepositories_Get(t *testing.T) { // TestRepositories_All ensures repos are returned from // .All accessor. Tests both builtin and buitlin+extensible cases. func TestRepositories_All(t *testing.T) { + skipIfNoGit(t) // see docs uri := ServeRepo(RepositoriesTestRepo, t) root, rm := Mktemp(t) defer rm() @@ -106,6 +109,7 @@ func TestRepositories_All(t *testing.T) { // TestRepositories_Add checks basic adding of a repository by URI. func TestRepositories_Add(t *testing.T) { + skipIfNoGit(t) // see docs uri := ServeRepo(RepositoriesTestRepo, t) // ./testdata/$RepositoriesTestRepo.git root, rm := Mktemp(t) // create and cd to a temp dir, returning path. defer rm() @@ -140,6 +144,7 @@ func TestRepositories_Add(t *testing.T) { // TestRepositories_AddDefaultName ensures that repository name is optional, // by default being set to the name of the repoisotory from the URI. func TestRepositories_AddDeafultName(t *testing.T) { + skipIfNoGit(t) // see docs // The test repository is the "base case" repo, which is a manifestless // repo meant to exemplify the simplest use case: a repo with no metadata // that simply contains templates, grouped by runtime. It therefore does @@ -175,6 +180,7 @@ func TestRepositories_AddDeafultName(t *testing.T) { // a manfest wherein a default name is specified, is used as the name for the // added repository when a name is not explicitly specified. func TestRepositories_AddWithManifest(t *testing.T) { + skipIfNoGit(t) // see docs // repository-b is meant to exemplify the use case of a repository which // defines a custom language pack and makes full use of the manifest.yaml. // The manifest.yaml is included which specifies things like custom templates @@ -210,6 +216,7 @@ func TestRepositories_AddWithManifest(t *testing.T) { // TestRepositories_AddExistingErrors ensures that adding a repository that // already exists yields an error. func TestRepositories_AddExistingErrors(t *testing.T) { + skipIfNoGit(t) // see docs uri := ServeRepo(RepositoriesTestRepo, t) root, rm := Mktemp(t) // create and cd to a temp dir, returning path. defer rm() @@ -244,6 +251,7 @@ func TestRepositories_AddExistingErrors(t *testing.T) { // TestRepositories_Rename ensures renaming a repository succeeds. func TestRepositories_Rename(t *testing.T) { + skipIfNoGit(t) // see docs uri := ServeRepo(RepositoriesTestRepo, t) root, rm := Mktemp(t) // create and cd to a temp dir, returning path. defer rm() @@ -278,6 +286,7 @@ func TestRepositories_Rename(t *testing.T) { // TestRepositories_Remove ensures that removing a repository by name // removes it from the list and FS. func TestRepositories_Remove(t *testing.T) { + skipIfNoGit(t) // see docs uri := ServeRepo(RepositoriesTestRepo, t) // ./testdata/repository.git root, rm := Mktemp(t) // create and cd to a temp dir defer rm() @@ -313,6 +322,7 @@ func TestRepositories_Remove(t *testing.T) { // TestRepositories_URL ensures that a repository populates its URL member // from the git repository's origin url (if it is a git repo and exists) func TestRepositories_URL(t *testing.T) { + skipIfNoGit(t) // see docs uri := ServeRepo(RepositoriesTestRepo, t) root, rm := Mktemp(t) defer rm() @@ -353,3 +363,34 @@ func TestRepositories_Missing(t *testing.T) { t.Fatal(err) } } + +// skipIfNoGit skips the test if there is no 'git' available in PATH. +// +// The 'go-git' dependency only has partial support for file:// paths. +// see: https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md +// Therefore the "git" binary is used as a fallback. +// +// These file:// paths we rely on for cloning repositories from the +// inbuilt repositories. Therefore, it is not a "pure-go" implementation +// of git. This results in there being a hard dependency on the "git" +// program, which this library uses as a fallback. +// TODO: We can either 1) reimplement this functionality ourselves +// by capturing file:// paths and just copying the files (we don't need +// the git repository; just the files), 2) find a library which +// does actually implmement this part of git without relying on "git", +// 3) submit a patch upstream which implements this functionality. +// 4) At least submit a patch upstream allowing the path of the git bin +// to be specified when no PATH. +// Temporary workaround: Detect when this test is being run alongside +// the integration/e2e tests (which clear the environment), and skip. +func skipIfNoGit(t *testing.T) { + // integration and e2e tests clear their environment in order to + // obtain isolation. If there is no 'git' available; skip the test. + // Note that our libraries accept an environmental override for these + // binaries (FUNC_E2E_GIT for example). There is no such setting for + // the go-git dependency. + _, err := exec.LookPath("git") + if err != nil { + t.Skip("No 'git' found in path. Skipping test.") + } +} diff --git a/pkg/functions/repository_test.go b/pkg/functions/repository_test.go index b7e958fe3..c5c344c0f 100644 --- a/pkg/functions/repository_test.go +++ b/pkg/functions/repository_test.go @@ -15,6 +15,7 @@ import ( // TestRepository_TemplatesPath ensures that repositories can specify // an alternate location for templates using a manifest. func TestRepository_TemplatesPath(t *testing.T) { + skipIfNoGit(t) // see docs client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) // The repo ./testdata/repositories/customLanguagePackRepo includes a @@ -40,6 +41,7 @@ func TestRepository_TemplatesPath(t *testing.T) { // and template level. The tests check for both embedded structures: // HealthEndpoints BuildConfig. func TestRepository_Inheritance(t *testing.T) { + skipIfNoGit(t) // see docs var err error client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) diff --git a/pkg/functions/templates_test.go b/pkg/functions/templates_test.go index 64351e9b2..879d62b5e 100644 --- a/pkg/functions/templates_test.go +++ b/pkg/functions/templates_test.go @@ -19,6 +19,7 @@ import ( // TestTemplates_List ensures that all templates are listed taking into account // both internal and extensible (prefixed) repositories. func TestTemplates_List(t *testing.T) { + skipIfNoGit(t) // see docs // A client which specifies a location of exensible repositoreis on disk // will list all builtin plus exensible client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) @@ -48,6 +49,8 @@ func TestTemplates_List(t *testing.T) { // when retrieving the list of templates for a runtime that does not exist // in an extended repository, but does in the default. func TestTemplates_List_ExtendedNotFound(t *testing.T) { + skipIfNoGit(t) // see docs + // An external template repo which does not contain python client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) @@ -72,6 +75,7 @@ func TestTemplates_List_ExtendedNotFound(t *testing.T) { // TestTemplates_Get ensures that a template's metadata object can // be retrieved by full name (full name prefix optional for embedded). func TestTemplates_Get(t *testing.T) { + skipIfNoGit(t) // see docs client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) // Check embedded @@ -127,6 +131,7 @@ func TestTemplates_Embedded(t *testing.T) { // (ie. custom provider on disk) can be specified as the source for a // template. func TestTemplates_Custom(t *testing.T) { + skipIfNoGit(t) // see docs // Create test directory root := "testdata/testTemplatesCustom" defer Using(t, root)() @@ -160,6 +165,7 @@ func TestTemplates_Custom(t *testing.T) { // can be specificed on creation of client, with subsequent calls to Create // using this remote by default. func TestTemplates_Remote(t *testing.T) { + skipIfNoGit(t) // see docs var err error root := "testdata/testTemplatesRemote" @@ -285,6 +291,7 @@ func TestTemplates_ModeEmbedded(t *testing.T) { // TestTemplates_ModeCustom ensures that templates written from custom templates // retain their mode. func TestTemplates_ModeCustom(t *testing.T) { + skipIfNoGit(t) // see docs if runtime.GOOS == "windows" { return // not applicable } @@ -320,6 +327,7 @@ func TestTemplates_ModeCustom(t *testing.T) { // TestTemplates_ModeRemote ensures that templates written from remote templates // retain their mode. func TestTemplates_ModeRemote(t *testing.T) { + skipIfNoGit(t) // see docs var err error if runtime.GOOS == "windows" { @@ -370,6 +378,7 @@ func TestTemplates_ModeRemote(t *testing.T) { // TestTemplates_RuntimeManifestBuildEnvs ensures that BuildEnvs specified in a // runtimes's manifest are included in the final function. func TestTemplates_RuntimeManifestBuildEnvs(t *testing.T) { + skipIfNoGit(t) // see docs // create test directory root := "testdata/testTemplatesRuntimeManifestBuildEnvs" defer Using(t, root)() @@ -417,6 +426,7 @@ func TestTemplates_RuntimeManifestBuildEnvs(t *testing.T) { // TestTemplates_ManifestBuildEnvs ensures that BuildEnvs specified in a // template's manifest are included in the final function. func TestTemplates_ManifestBuildEnvs(t *testing.T) { + skipIfNoGit(t) // see docs // create test directory root := "testdata/testTemplatesManifestBuildEnvs" defer Using(t, root)() @@ -464,6 +474,7 @@ func TestTemplates_ManifestBuildEnvs(t *testing.T) { // TestTemplates_RepositoryManifestBuildEnvs ensures that BuildEnvs specified in a // repository's manifest are included in the final function. func TestTemplates_RepositoryManifestBuildEnvs(t *testing.T) { + skipIfNoGit(t) // see docs // create test directory root := "testdata/testRepositoryManifestBuildEnvs" defer Using(t, root)() @@ -511,6 +522,7 @@ func TestTemplates_RepositoryManifestBuildEnvs(t *testing.T) { // TestTemplates_ManifestInvocationHints ensures that invocation hints // from a template's manifest are included in the final function. func TestTemplates_ManifestInvocationHints(t *testing.T) { + skipIfNoGit(t) // see docs root := "testdata/testTemplatesManifestInvocationHints" defer Using(t, root)() @@ -535,6 +547,7 @@ func TestTemplates_ManifestInvocationHints(t *testing.T) { // TestTemplates_ManifestRemoved ensures that the manifest is not left in // the resultant function after write. func TestTemplates_ManifestRemoved(t *testing.T) { + skipIfNoGit(t) // see docs // create test directory root := "testdata/testTemplateManifestRemoved" defer Using(t, root)() @@ -571,6 +584,7 @@ func TestTemplates_ManifestRemoved(t *testing.T) { // does not define an invocation hint defaults to empty string (since 0.35.0 // default value is omitted from func.yaml file for Invoke) func TestTemplates_InvocationDefault(t *testing.T) { + skipIfNoGit(t) // see docs expectedInvoke := "" root := "testdata/testTemplatesInvocationDefault" defer Using(t, root)() diff --git a/pkg/functions/testdata/default_home/README.md b/pkg/functions/testdata/default_home/README.md new file mode 100644 index 000000000..9ede15a35 --- /dev/null +++ b/pkg/functions/testdata/default_home/README.md @@ -0,0 +1,3 @@ +# default_home + +For use by tests which isolate their environment and need a HOME diff --git a/pkg/oci/go_builder.go b/pkg/oci/go_builder.go index c14eaf675..f6fdad409 100644 --- a/pkg/oci/go_builder.go +++ b/pkg/oci/go_builder.go @@ -127,8 +127,8 @@ func goBuildCmd(p v1.Platform, cfg buildJob) (gobin string, args []string, outpa * Either replace or append to gobin */ - // Use the binary specified FUNC_GO_PATH if defined - gobin = os.Getenv("FUNC_GO_PATH") // TODO: move to main and plumb through + // Use the binary specified FUNC_GO if defined + gobin = os.Getenv("FUNC_GO") // TODO: move to main and plumb through if gobin == "" { gobin = "go" }