//go:build !integration // +build !integration package functions_test import ( "errors" "os" "path/filepath" "runtime" "testing" "github.com/google/go-cmp/cmp" fn "knative.dev/func/pkg/functions" . "knative.dev/func/pkg/testing" ) // TestTemplates_List ensures that all templates are listed taking into account // both internal and extensible (prefixed) repositories. func TestTemplates_List(t *testing.T) { // A client which specifies a location of exensible repositoreis on disk // will list all builtin plus exensible client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) // list templates for the "go" runtime templates, err := client.Templates().List("go") if err != nil { t.Fatal(err) } // Note that this list will change as the customTemplateRepo // and builtin templates are shared. THis could be mitigated // by creating a custom repository path for just this test, if // that becomes a hassle. expected := []string{ "cloudevents", "http", "customTemplateRepo/customTemplate", } if diff := cmp.Diff(expected, templates); diff != "" { t.Error("Unexpected templates (-want, +got):", diff) } } // TestTemplates_List_ExtendedNotFound ensures that an error is not returned // 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) { // An external template repo which does not contain python client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) // list templates for the "python" runtime, which will be found // in the embedded filesystem. templates, err := client.Templates().List("python") if err != nil { t.Fatal(err) // there shouldb be no error.. } // and the list should be those from the embedded fs expected := []string{ "cloudevents", "http", } if diff := cmp.Diff(expected, templates); diff != "" { t.Error("Unexpected templates (-want, +got):", diff) } } // 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) { client := fn.New(fn.WithRepositoriesPath("testdata/repositories")) // Check embedded embedded, err := client.Templates().Get("go", "http") if err != nil { t.Fatal(err) } if embedded.Runtime() != "go" || embedded.Repository() != "default" || embedded.Name() != "http" { t.Logf("Expected template from embedded to have runtime 'go' repo 'default' name 'http', got '%v', '%v', '%v',", embedded.Runtime(), embedded.Repository(), embedded.Name()) } // Check extended extended, err := client.Templates().Get("go", "customTemplateRepo/customTemplate") if err != nil { t.Fatal(err) } if embedded.Runtime() != "go" || embedded.Repository() != "default" || embedded.Name() != "http" { t.Logf("Expected template from extended repo to have runtime 'go' repo 'customTemplateRepo' name 'customTemplate', got '%v', '%v', '%v',", extended.Runtime(), extended.Repository(), extended.Name()) } } // TestTemplates_Embedded ensures that embedded templates are copied on write. func TestTemplates_Embedded(t *testing.T) { // create test directory root := "testdata/testTemplatesEmbedded" defer Using(t, root)() // Client whose internal (builtin default) templates will be used. client := fn.New(fn.WithRegistry(TestRegistry)) // write out a template _, err := client.Init(fn.Function{ Root: root, Runtime: TestRuntime, Template: "http", }) if err != nil { t.Fatal(err) } // Assert file exists as expected _, err = os.Stat(filepath.Join(root, "handle.go")) if err != nil { t.Fatal(err) } } // TestTemplates_Custom ensures that a template from a filesystem source // (ie. custom provider on disk) can be specified as the source for a // template. func TestTemplates_Custom(t *testing.T) { // Create test directory root := "testdata/testTemplatesCustom" defer Using(t, root)() // CLient which uses custom repositories // in form [provider]/[template], on disk the template is // at: testdata/repositories/[provider]/[runtime]/[template] client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) // Create a function specifying a template from // the custom provider's directory in the on-disk template repo. _, err := client.Init(fn.Function{ Root: root, Runtime: "customRuntime", Template: "customTemplateRepo/customTemplate", }) if err != nil { t.Fatal(err) } // Assert file exists as expected _, err = os.Stat(filepath.Join(root, "custom.impl")) if err != nil { t.Fatal(err) } } // TestTemplates_Remote ensures that a Git template repository provided via URI // can be specificed on creation of client, with subsequent calls to Create // using this remote by default. func TestTemplates_Remote(t *testing.T) { var err error root := "testdata/testTemplatesRemote" defer Using(t, root)() url := ServeRepo(RepositoriesTestRepo, t) // Create a client which explicitly specifies the Git repo at URL // rather than relying on the default internally builtin template repo client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepository(url)) // Create a default function, which should override builtin and use // that from the specified url (git repo) _, err = client.Init(fn.Function{ Root: root, Runtime: "go", Template: "remote", }) if err != nil { t.Fatal(err) } // Assert the sample file from the git repo was written _, err = os.Stat(filepath.Join(root, "remote-test")) if err != nil { t.Fatal(err) } } // TestTemplates_Default ensures that the expected default template // is used when none specified. func TestTemplates_Default(t *testing.T) { // create test directory root := "testdata/testTemplates_Default" defer Using(t, root)() client := fn.New(fn.WithRegistry(TestRegistry)) // The runtime is specified, and explicitly includes a // file for the default template of fn.DefaultTemplate _, err := client.Init(fn.Function{Root: root, Runtime: TestRuntime}) if err != nil { t.Fatal(err) } // Assert file exists as expected _, err = os.Stat(filepath.Join(root, "handle.go")) if err != nil { t.Fatal(err) } } // TestTemplates_InvalidErrors ensures that specifying unrecgognized // runtime/template errors func TestTemplates_InvalidErrors(t *testing.T) { // create test directory root := "testdata/testTemplates_InvalidErrors" defer Using(t, root)() client := fn.New(fn.WithRegistry(TestRegistry)) // Error will be type-checked. var err error // Test for error writing an invalid runtime _, err = client.Init(fn.Function{ Root: root, Runtime: "invalid", }) if !errors.Is(err, fn.ErrRuntimeNotFound) { t.Fatalf("Expected ErrRuntimeNotFound, got %v", err) } os.Remove(filepath.Join(root, ".gitignore")) // Test for error writing an invalid template _, err = client.Init(fn.Function{ Root: root, Runtime: TestRuntime, Template: "invalid", }) if !errors.Is(err, fn.ErrTemplateNotFound) { t.Fatalf("Expected ErrTemplateNotFound, got %v", err) } } // TestTemplates_ModeEmbedded ensures that templates written from the embedded // templates retain their mode. func TestTemplates_ModeEmbedded(t *testing.T) { if runtime.GOOS == "windows" { return // not applicable } // set up test directory root := "testdata/testTemplatesModeEmbedded" defer Using(t, root)() client := fn.New(fn.WithRegistry(TestRegistry)) // Write the embedded template that contains a file which // needs to be executable (only such is mvnw in quarkus) _, err := client.Init(fn.Function{ Root: root, Runtime: "quarkus", Template: "http", }) if err != nil { t.Fatal(err) } // Verify file mode was preserved file, err := os.Stat(filepath.Join(root, "mvnw")) if err != nil { t.Fatal(err) } if file.Mode() != os.FileMode(0755) { t.Fatalf("The embedded executable's mode should be 0755 but was %v", file.Mode()) } } // TestTemplates_ModeCustom ensures that templates written from custom templates // retain their mode. func TestTemplates_ModeCustom(t *testing.T) { if runtime.GOOS == "windows" { return // not applicable } // test directories root := "testdata/testTemplates_ModeCustom" defer Using(t, root)() client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) // Write executable from custom repo _, err := client.Init(fn.Function{ Root: root, Runtime: "test", Template: "customTemplateRepo/tplb", }) if err != nil { t.Fatal(err) } // Verify custom file mode was preserved. file, err := os.Stat(filepath.Join(root, "executable.sh")) if err != nil { t.Fatal(err) } if file.Mode() != os.FileMode(0755) { t.Fatalf("The custom executable file's mode should be 0755 but was %v", file.Mode()) } } // TestTemplates_ModeRemote ensures that templates written from remote templates // retain their mode. func TestTemplates_ModeRemote(t *testing.T) { var err error if runtime.GOOS == "windows" { return // not applicable } // test directories root := "testdata/testTemplates_ModeRemote" defer Using(t, root)() url := ServeRepo(RepositoriesTestRepo, t) client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepository(url)) // Write executable from custom repo _, err = client.Init(fn.Function{ Root: root, Runtime: "node", Template: "remote", }) if err != nil { t.Fatal(err) } // Verify directory file mode was preserved file, err := os.Stat(filepath.Join(root, "test")) if err != nil { t.Fatal(err) } if file.Mode() != os.ModeDir|0755 { t.Fatalf("The remote repositry directory mode should be 0755 but was %#o", file.Mode()) } // Verify remote executable file mode was preserved. file, err = os.Stat(filepath.Join(root, "test", "executable.sh")) if err != nil { t.Fatal(err) } if file.Mode() != os.FileMode(0755) { t.Fatalf("The remote executable's mode should be 0755 but was %v", file.Mode()) } } // TODO: test typed errors for custom and remote (embedded checked) // TestTemplates_RuntimeManifestBuildEnvs ensures that BuildEnvs specified in a // runtimes's manifest are included in the final function. func TestTemplates_RuntimeManifestBuildEnvs(t *testing.T) { // create test directory root := "testdata/testTemplatesRuntimeManifestBuildEnvs" defer Using(t, root)() // Client whose internal templates will be used. client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) // write out a template _, err := client.Init(fn.Function{ Root: root, Runtime: "manifestedRuntime", Template: "customLanguagePackRepo/customTemplate", }) if err != nil { t.Fatal(err) } // Assert file exists as expected _, err = os.Stat(filepath.Join(root, "func.yaml")) if err != nil { t.Fatal(err) } testVariableName := "TEST_RUNTIME_VARIABLE" testVariableValue := "test-runtime" envs := []fn.Env{ { Name: &testVariableName, Value: &testVariableValue, }, } f, err := fn.NewFunction(root) if err != nil { t.Fatal(err) } if diff := cmp.Diff(fn.Envs(envs), f.Build.BuildEnvs); diff != "" { t.Fatalf("Unexpected difference between runtime's manifest.yaml buildEnvs and function BuildEnvs (-want, +got): %v", diff) } } // TestTemplates_ManifestBuildEnvs ensures that BuildEnvs specified in a // template's manifest are included in the final function. func TestTemplates_ManifestBuildEnvs(t *testing.T) { // create test directory root := "testdata/testTemplatesManifestBuildEnvs" defer Using(t, root)() // Client whose internal templates will be used. client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) // write out a template _, err := client.Init(fn.Function{ Root: root, Runtime: "manifestedRuntime", Template: "customLanguagePackRepo/manifestedTemplate", }) if err != nil { t.Fatal(err) } // Assert file exists as expected _, err = os.Stat(filepath.Join(root, "func.yaml")) if err != nil { t.Fatal(err) } testVariableName := "TEST_TEMPLATE_VARIABLE" testVariableValue := "test-template" envs := []fn.Env{ { Name: &testVariableName, Value: &testVariableValue, }, } f, err := fn.NewFunction(root) if err != nil { t.Fatal(err) } if diff := cmp.Diff(fn.Envs(envs), f.Build.BuildEnvs); diff != "" { t.Fatalf("Unexpected difference between template's manifest.yaml buildEnvs and function BuildEnvs (-want, +got): %v", diff) } } // TestTemplates_RepositoryManifestBuildEnvs ensures that BuildEnvs specified in a // repository's manifest are included in the final function. func TestTemplates_RepositoryManifestBuildEnvs(t *testing.T) { // create test directory root := "testdata/testRepositoryManifestBuildEnvs" defer Using(t, root)() // Client whose internal templates will be used. client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) // write out a template _, err := client.Init(fn.Function{ Root: root, Runtime: "customRuntime", Template: "customLanguagePackRepo/customTemplate", }) if err != nil { t.Fatal(err) } // Assert file exists as expected _, err = os.Stat(filepath.Join(root, "func.yaml")) if err != nil { t.Fatal(err) } testVariableName := "TEST_REPO_VARIABLE" testVariableValue := "test-repo" envs := []fn.Env{ { Name: &testVariableName, Value: &testVariableValue, }, } f, err := fn.NewFunction(root) if err != nil { t.Fatal(err) } if diff := cmp.Diff(fn.Envs(envs), f.Build.BuildEnvs); diff != "" { t.Fatalf("Unexpected difference between repository's manifest.yaml buildEnvs and function BuildEnvs (-want, +got): %v", diff) } } // TestTemplates_ManifestInvocationHints ensures that invocation hints // from a template's manifest are included in the final function. func TestTemplates_ManifestInvocationHints(t *testing.T) { root := "testdata/testTemplatesManifestInvocationHints" defer Using(t, root)() client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) f, err := client.Init(fn.Function{ Root: root, Runtime: "manifestedRuntime", Template: "customLanguagePackRepo/manifestedTemplate", }) if err != nil { t.Fatal(err) } if f.Invoke != "format" { t.Fatalf("expected invoke format 'format', got '%v'", f.Invoke) } } // TestTemplates_ManifestRemoved ensures that the manifest is not left in // the resultant function after write. func TestTemplates_ManifestRemoved(t *testing.T) { // create test directory root := "testdata/testTemplateManifestRemoved" defer Using(t, root)() // Client whose internal templates will be used. client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) // write out a template _, err := client.Init(fn.Function{ Root: root, Runtime: "manifestedRuntime", Template: "customLanguagePackRepo/manifestedTemplate", }) if err != nil { t.Fatal(err) } // Assert func.yaml exists as expected _, err = os.Stat(filepath.Join(root, "func.yaml")) if err != nil { t.Fatal(err) } // Assert manifest.yaml does not _, err = os.Stat(filepath.Join(root, "manifest.yaml")) if err == nil { t.Fatal("manifest.yaml should not exist after write") } } // TestTemplates_InvocationDefault ensures that creating a function which // 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) { expectedInvoke := "" root := "testdata/testTemplatesInvocationDefault" defer Using(t, root)() client := fn.New( fn.WithRegistry(TestRegistry), fn.WithRepositoriesPath("testdata/repositories")) // The customTemplateRepo explicitly does not // include manifests as it exemplifies an entirely default template repo. f, err := client.Init(fn.Function{ Root: root, Runtime: "customRuntime", Template: "customTemplateRepo/customTemplate", }) if err != nil { t.Fatal(err) } if f.Invoke != expectedInvoke { t.Fatalf("expected '%v' invoke format. Got '%v'", expectedInvoke, f.Invoke) } }