mirror of https://github.com/knative/func.git
394 lines
11 KiB
Go
394 lines
11 KiB
Go
package config_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"knative.dev/func/pkg/config"
|
|
fn "knative.dev/func/pkg/functions"
|
|
|
|
. "knative.dev/func/pkg/testing"
|
|
)
|
|
|
|
// TestNewDefaults ensures that the default Config
|
|
// constructor yelds a struct prepopulated with static
|
|
// defaults.
|
|
func TestNewDefaults(t *testing.T) {
|
|
cfg := config.New()
|
|
if cfg.Language != config.DefaultLanguage {
|
|
t.Fatalf("expected config's language = '%v', got '%v'", config.DefaultLanguage, cfg.Language)
|
|
}
|
|
}
|
|
|
|
// TestLoad ensures that loading a config reads values
|
|
// in from a config file at path, and in this case (unlike NewDefault) the
|
|
// file must exist at path or error.
|
|
func TestLoad(t *testing.T) {
|
|
cfg, err := config.Load(filepath.Join("testdata", "TestLoad", "func", "config.yaml"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Language != "custom" {
|
|
t.Fatalf("loaded config did not contain values from config file. Expected \"custom\" got \"%v\"", cfg.Language)
|
|
}
|
|
|
|
// and ensure error
|
|
cfg, err = config.Load("invalid/path")
|
|
if err == nil {
|
|
t.Fatal("did not receive expected error loading nonexistent config path")
|
|
}
|
|
}
|
|
|
|
// TestWrite ensures that writing a config persists.
|
|
func TestWrite(t *testing.T) {
|
|
root, cleanup := Mktemp(t)
|
|
t.Cleanup(cleanup)
|
|
t.Setenv("XDG_CONFIG_HOME", root)
|
|
var err error
|
|
|
|
// Ensure error writing when config paths do not exist
|
|
cfg := config.New()
|
|
cfg.Language = "example"
|
|
if err = cfg.Write(config.File()); err == nil {
|
|
t.Fatal("did not receive error writing to a nonexistent path")
|
|
}
|
|
|
|
// Create the path and ensure writing generates no error
|
|
if err = config.CreatePaths(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = cfg.Write(config.File()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Confirm value was persisted
|
|
if cfg, err = config.Load(config.File()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Language != "example" {
|
|
t.Fatalf("config did not persist. expected 'example', got '%v'", cfg.Language)
|
|
}
|
|
}
|
|
|
|
// TestPath ensures that the Path accessor returns
|
|
// XDG_CONFIG_HOME/.config/func
|
|
func TestPath(t *testing.T) {
|
|
home := t.TempDir() // root of all configs
|
|
path := filepath.Join(home, "func") // our config
|
|
|
|
t.Setenv("XDG_CONFIG_HOME", home)
|
|
|
|
if config.Dir() != path {
|
|
t.Fatalf("expected config path '%v', got '%v'", path, config.Dir())
|
|
}
|
|
}
|
|
|
|
// TestNewDefault ensures that the default returned from NewDefault includes
|
|
// both the static defaults (see TestNewDefaults), as well as those from the
|
|
// currently effective global config path (~/config/func).
|
|
func TestNewDefault(t *testing.T) {
|
|
// Custom config home results in a config file default path of
|
|
// ./testdata/func/config.yaml
|
|
home := filepath.Join(Cwd(), "testdata")
|
|
t.Setenv("XDG_CONFIG_HOME", home)
|
|
|
|
cfg, err := config.NewDefault() // Should load values from above config
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if cfg.Language != "custom" {
|
|
t.Fatalf("config file not loaded")
|
|
}
|
|
}
|
|
|
|
// TestCreatePaths ensures that the paths are created when requested.
|
|
func TestCreatePaths(t *testing.T) {
|
|
home, cleanup := Mktemp(t)
|
|
t.Cleanup(cleanup)
|
|
|
|
t.Setenv("XDG_CONFIG_HOME", home)
|
|
|
|
if err := config.CreatePaths(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := os.Stat(config.Dir()); err != nil {
|
|
if os.IsNotExist(err) {
|
|
t.Fatalf("config path '%v' not created", config.Dir())
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := os.Stat(filepath.Join(config.Dir(), "repositories")); err != nil {
|
|
if os.IsNotExist(err) {
|
|
t.Fatalf("config path '%v' not created", config.Dir())
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Trying to create when repositories path is invalid should error
|
|
_ = os.WriteFile("./invalidRepositoriesPath.txt", []byte{}, os.ModePerm)
|
|
t.Setenv("FUNC_REPOSITORIES_PATH", "./invalidRepositoriesPath.txt")
|
|
if err := config.CreatePaths(); err == nil {
|
|
t.Fatal("did not receive error when creating paths with an invalid FUNC_REPOSITORIES_PATH")
|
|
}
|
|
|
|
// Trying to Create config path should bubble errors, for example when HOME is
|
|
// set to a nonexistent path.
|
|
_ = os.WriteFile("./invalidConfigHome.txt", []byte{}, os.ModePerm)
|
|
t.Setenv("XDG_CONFIG_HOME", "./invalidConfigHome.txt")
|
|
if err := config.CreatePaths(); err == nil {
|
|
t.Fatal("did not receive error when creating paths in an invalid home")
|
|
}
|
|
}
|
|
|
|
// TestNewDefault_ConfigNotRequired ensures that when creating a new
|
|
// config which would load a global config, its nonexistence causes no error.
|
|
func TestNewDefault_ConfigNotRequired(t *testing.T) {
|
|
// Custom config home results in a config file default path of
|
|
// ./testdata/func/config.yaml
|
|
home, cleanup := Mktemp(t)
|
|
t.Cleanup(cleanup)
|
|
t.Setenv("XDG_CONFIG_HOME", home)
|
|
|
|
_, err := config.NewDefault() // Should not error despite no config.
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestRepositoriesPath returns the path expected
|
|
// (XDG_CONFIG_HOME/func/repositories by default)
|
|
func TestRepositoriesPath(t *testing.T) {
|
|
home, cleanup := Mktemp(t)
|
|
t.Cleanup(cleanup)
|
|
t.Setenv("XDG_CONFIG_HOME", home)
|
|
|
|
expected := filepath.Join(home, "func", config.Repositories)
|
|
if config.RepositoriesPath() != expected {
|
|
t.Fatalf("unexpected reposiories path: %v", config.RepositoriesPath())
|
|
}
|
|
}
|
|
|
|
// TestApply ensures that applying a function as context to a config results
|
|
// in every member of config in the intersection of the two sets, global config
|
|
// and function, to be set to the values of the function.
|
|
// (See the associated cfg.Configure)
|
|
func TestApply(t *testing.T) {
|
|
// Yes, every member needs to be painstakingly enumerated by hand, because
|
|
// the sets are not equivalent. Not all global settings have an associated
|
|
// member on the function (example: confirm), and not all members of a
|
|
// function are globally configurable (example: image).
|
|
f := fn.Function{
|
|
Build: fn.BuildSpec{
|
|
Builder: "builder",
|
|
},
|
|
Deploy: fn.DeploySpec{
|
|
Namespace: "namespace",
|
|
},
|
|
Runtime: "runtime",
|
|
Registry: "registry",
|
|
}
|
|
cfg := config.Global{}.Apply(f)
|
|
|
|
if cfg.Builder != "builder" {
|
|
t.Error("apply missing map of f.Build.Builder")
|
|
}
|
|
if cfg.Language != "runtime" {
|
|
t.Error("apply missing map of f.Runtime ")
|
|
}
|
|
// Note that namespace is handled manually in the clients because
|
|
// active k8s context must be taken into account. This may
|
|
// be merged back into this config package in the future, but for now
|
|
// "applying" a function's state onto a config will not alter
|
|
// the namespace value, because it's not a simple mapping.
|
|
// if cfg.Namespace != "namespace" {
|
|
// t.Error("apply missing map of f.Namespace")
|
|
// }
|
|
if cfg.Registry != "registry" {
|
|
t.Error("apply missing map of f.Registry")
|
|
}
|
|
|
|
// empty values in the function context should not zero out
|
|
// populated values in the global config when applying.
|
|
cfg.Apply(fn.Function{})
|
|
if cfg.Builder == "" {
|
|
t.Error("empty f.Build.Builder should not be mapped")
|
|
}
|
|
if cfg.Language == "" {
|
|
t.Error("empty f.Runtime should not be mapped")
|
|
}
|
|
if cfg.Registry == "" {
|
|
t.Error("empty f.Registry should not be mapped")
|
|
}
|
|
}
|
|
|
|
// TestConfigyre ensures that configuring a function results in every member
|
|
// of the function in the intersection of the two sets, global config and function
|
|
// members, to be set to the values of the config.
|
|
// (See the associated cfg.Apply)
|
|
func TestConfigure(t *testing.T) {
|
|
f := fn.Function{}
|
|
cfg := config.Global{
|
|
Builder: "builder",
|
|
Language: "runtime",
|
|
Namespace: "namespace",
|
|
Registry: "registry",
|
|
}
|
|
f = cfg.Configure(f)
|
|
|
|
if f.Build.Builder != "builder" {
|
|
t.Error("configure missing map for f.Build.Builder")
|
|
}
|
|
if f.Deploy.Namespace != "namespace" {
|
|
t.Error("configure missing map for f.Deploy.Namespace")
|
|
}
|
|
if f.Runtime != "runtime" {
|
|
t.Error("configure missing map for f.Language")
|
|
}
|
|
if f.Registry != "registry" {
|
|
t.Error("configure missing map for f.Registry")
|
|
}
|
|
|
|
// empty values in the global config shoul not zero out function values
|
|
// when configuring.
|
|
f = config.Global{}.Configure(f)
|
|
if f.Build.Builder == "" {
|
|
t.Error("empty cfg.Builder should not mutate f")
|
|
}
|
|
if f.Deploy.Namespace == "" {
|
|
t.Error("empty cfg.Namespace should not mutate f")
|
|
}
|
|
if f.Runtime == "" {
|
|
t.Error("empty cfg.Runtime should not mutate f")
|
|
}
|
|
if f.Registry == "" {
|
|
t.Error("empty cfg.Registry should not mutate f")
|
|
}
|
|
|
|
}
|
|
|
|
// TestGet_Invalid ensures that attempting to get the value of a nonexistent
|
|
// member returns nil.
|
|
func TestGet_Invalid(t *testing.T) {
|
|
v := config.Get(config.Global{}, "invalid")
|
|
if v != nil {
|
|
t.Fatalf("expected accessing a nonexistent member to return nil, but got: %v", v)
|
|
}
|
|
}
|
|
|
|
// TestGet_Valid ensures a valid field name returns the value for that field.
|
|
// Name is keyed off the yaml serialization key of the field rather than the
|
|
// (capitalized) exported member name of the struct in order to be consistent
|
|
// with the disk-serialized config file format, and thus integrate nicely with
|
|
// CLIs, etc.
|
|
func TestGet_Valid(t *testing.T) {
|
|
c := config.Global{
|
|
Builder: "myBuilder",
|
|
Confirm: true,
|
|
}
|
|
// Get String
|
|
v := config.Get(c, "builder")
|
|
if v != "myBuilder" {
|
|
t.Fatalf("Did not receive expected value for builder. got: %v", v)
|
|
}
|
|
// Get Boolean
|
|
v = config.Get(c, "confirm")
|
|
if v != true {
|
|
t.Fatalf("Did not receive expected value for builder. got: %v", v)
|
|
}
|
|
}
|
|
|
|
// TestSet_Invalid ensures that attemptint to set an invalid field errors.
|
|
func TestSet_Invalid(t *testing.T) {
|
|
_, err := config.SetString(config.Global{}, "invalid", "foo")
|
|
if err == nil {
|
|
t.Fatal("did not receive expected error setting a nonexistent field")
|
|
}
|
|
}
|
|
|
|
// TestSet_ValidTyped ensures that attempting to set attributes with valid
|
|
// names and typed values succeeds.
|
|
func TestSet_ValidTyped(t *testing.T) {
|
|
cfg := config.Global{}
|
|
|
|
// Set a String
|
|
cfg, err := config.SetString(cfg, "builder", "myBuilder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Builder != "myBuilder" {
|
|
t.Fatalf("unexpected value for config builder: %v", cfg.Builder)
|
|
}
|
|
|
|
// Set a Bool
|
|
cfg, err = config.SetBool(cfg, "confirm", true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Builder != "myBuilder" {
|
|
t.Fatalf("unexpected value for config builder: %v", cfg.Builder)
|
|
}
|
|
|
|
// TODO lazily populate typed accessors if/when global config expands to
|
|
// include types of additional values.
|
|
}
|
|
|
|
// TestSet_ValidStrings ensures that setting valid attribute names using
|
|
// the string representation of their values succeeds.
|
|
func TestSet_ValidStrings(t *testing.T) {
|
|
cfg := config.Global{}
|
|
|
|
// Set a String from a string
|
|
// should be the base case
|
|
cfg, err := config.Set(cfg, "builder", "myBuilder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Builder != "myBuilder" {
|
|
t.Fatalf("unexpected value for config builder: %v", cfg.Builder)
|
|
}
|
|
|
|
// Set a Bool
|
|
cfg, err = config.SetBool(cfg, "confirm", true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Builder != "myBuilder" {
|
|
t.Fatalf("unexpected value for config builder: %v", cfg.Builder)
|
|
}
|
|
|
|
// TODO: lazily populate support of additional types in the implementation
|
|
// as needed.
|
|
}
|
|
|
|
// TestList ensures that the expected result is returned when listing
|
|
// the current names and values of the global config.
|
|
// The name is the name that can be used with Get and Set. The value is the
|
|
// string serialization of the value for the given name.
|
|
func TestList(t *testing.T) {
|
|
values := config.List()
|
|
expected := []string{
|
|
"builder",
|
|
"confirm",
|
|
"language",
|
|
"namespace",
|
|
"registry",
|
|
"registryInsecure",
|
|
"verbose",
|
|
}
|
|
|
|
if !reflect.DeepEqual(values, expected) {
|
|
t.Logf("expected:\n%v", expected)
|
|
t.Logf("received:\n%v", values)
|
|
t.Fatalf("unexpected list of configurable options.")
|
|
}
|
|
|
|
// NOTE: due to the strictness of this test, a new slice member will need
|
|
// to be added for each new field added to global config.
|
|
|
|
}
|