diff --git a/testutils/metahelper/client/client.go b/testutils/metahelper/client/client.go new file mode 100644 index 000000000..87221cb99 --- /dev/null +++ b/testutils/metahelper/client/client.go @@ -0,0 +1,109 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package client supports various needs for running tests +package client + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "path" + + "knative.dev/pkg/test/prow" +) + +const ( + filename = "metadata.json" +) + +// client holds metadata as a string:string map, as well as path for storing +// metadata +type client struct { + MetaData map[string]string + Path string +} + +// NewClient creates a client, takes custom directory for storing `metadata.json`. +// It reads existing `metadata.json` file if it exists, otherwise creates it. +// Errors out if there is any file i/o problem other than file not exist error. +func NewClient(dir string) (*client, error) { + c := &client{ + MetaData: make(map[string]string), + } + if dir == "" { + log.Println("Getting artifacts dir from prow") + dir = prow.GetLocalArtifactsDir() + } + c.Path = path.Join(dir, filename) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err = os.MkdirAll(dir, 0777); err != nil { + return nil, fmt.Errorf("Failed to create directory: %v", err) + } + } + return c, nil +} + +// sync is shared by Get and Set, invoked at the very beginning of each, makes +// sure the file exists, and loads the content of file into c.MetaData +func (c *client) sync() error { + _, err := os.Stat(c.Path) + if os.IsNotExist(err) { + body, _ := json.Marshal(&c.MetaData) + err = ioutil.WriteFile(c.Path, body, 0777) + } else { + var body []byte + body, err = ioutil.ReadFile(c.Path) + if err == nil { + err = json.Unmarshal(body, &c.MetaData) + } + } + + return err +} + +// Set sets key:val pair, and overrides if it exists +func (c *client) Set(key, val string) error { + err := c.sync() + if err != nil { + return err + } + if oldVal, ok := c.MetaData[key]; ok { + log.Printf("Overriding meta %q:%q with new value %q", key, oldVal, val) + } + c.MetaData[key] = val + body, _ := json.Marshal(c.MetaData) + return ioutil.WriteFile(c.Path, body, 0777) +} + +// Get gets val for key +func (c *client) Get(key string) (string, error) { + if _, err := os.Stat(c.Path); err != nil && os.IsNotExist(err) { + return "", fmt.Errorf("file %q doesn't exist", c.Path) + } + var res string + err := c.sync() + if err == nil { + if val, ok := c.MetaData[key]; ok { + res = val + } else { + err = fmt.Errorf("key %q doesn't exist", key) + } + } + return res, err +} diff --git a/testutils/metahelper/client/client_test.go b/testutils/metahelper/client/client_test.go new file mode 100644 index 000000000..e2d5baab8 --- /dev/null +++ b/testutils/metahelper/client/client_test.go @@ -0,0 +1,183 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package client supports various needs for running tests +package client + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "reflect" + "testing" +) + +const ( + fakeArtifactDir = "fakeArtifactDir" + mockArtifactEnv = "mockArtifactDir" +) + +func TestNewClient(t *testing.T) { + datas := []struct { + customDir string + expPath string + expErr bool + }{ + { // default dir + "", "mockArtifactDir/metadata.json", false, + }, { // custom dir + "a", "a/metadata.json", false, + }, + } + + for _, data := range datas { + dir := data.customDir + if data.customDir == "" { // use env var + oriArtifactDir := os.Getenv("ARTIFACTS") + defer os.Setenv("ARTIFACTS", oriArtifactDir) + os.Setenv("ARTIFACTS", mockArtifactEnv) + dir = mockArtifactEnv + } + os.RemoveAll(dir) + defer os.RemoveAll(dir) + c, err := NewClient(data.customDir) + errMsg := fmt.Sprintf("Testing new client with dir: %q", data.customDir) + if (err == nil && data.expErr) || (err != nil && !data.expErr) { + log.Fatalf("%s\ngot: '%v', want: '%v'", errMsg, err, data.expErr) + } + if c.Path != data.expPath { + log.Fatalf("%s\ngot: %q, want: %q", errMsg, c.Path, data.expPath) + } + if _, err := os.Stat(dir); os.IsNotExist(err) { + log.Fatalf("%s\nDirectory %q wasn't created", errMsg, dir) + } + + } +} + +func TestSync(t *testing.T) { + datas := []struct { + fileExist bool + content string + expMetadata map[string]string + expErr bool + }{ + { // file not exist + false, "", make(map[string]string), false, + }, { // file exist but empty + true, "", make(map[string]string), true, + }, { // file exist, invalid + true, "{", make(map[string]string), true, + }, { // file exist valid + true, "{}", make(map[string]string), false, + }, + } + + for _, data := range datas { + c, _ := NewClient(fakeArtifactDir) + os.Remove(c.Path) + if data.fileExist { + defer os.Remove(c.Path) + ioutil.WriteFile(c.Path, []byte(data.content), 0644) + } + err := c.sync() + errMsg := fmt.Sprintf("Testing syncing with file exist: %v, content: %q", data.fileExist, data.content) + if (err == nil && data.expErr) || (err != nil && !data.expErr) { + log.Fatalf("%s\ngot: '%v', want: '%v'", errMsg, err, data.expErr) + } + if !reflect.DeepEqual(c.MetaData, data.expMetadata) { + log.Fatalf("%s\ngot: '%v', want: '%v'", errMsg, c.MetaData, data.expMetadata) + } + } +} + +func TestSet(t *testing.T) { + datas := []struct { + metadata map[string]string + content string + setKey string + setVal string + expMetadata map[string]string + expErr bool + }{ + { // sync failed + make(map[string]string), "", "", "", make(map[string]string), true, + }, { // set normal key + make(map[string]string), "{}", "a", "b", map[string]string{"a": "b"}, false, + }, { // override + make(map[string]string), "{\"a\":\"b\"}", "a", "c", map[string]string{"a": "c"}, false, + }, { // ignore old client val + map[string]string{"a": "b"}, "{}", "c", "d", map[string]string{"c": "d"}, false, + }, + } + + for _, data := range datas { + c, _ := NewClient(fakeArtifactDir) + defer os.Remove(c.Path) + ioutil.WriteFile(c.Path, []byte(data.content), 0644) + + err := c.Set(data.setKey, data.setVal) + errMsg := fmt.Sprintf("Testing set %q:%q, with metadata: %v, content: %q", + data.setKey, data.setVal, data.metadata, data.content) + if (err == nil && data.expErr) || (err != nil && !data.expErr) { + log.Fatalf("%s\ngot: '%v', want: '%v'", errMsg, err, data.expErr) + } + if !reflect.DeepEqual(c.MetaData, data.expMetadata) { + log.Fatalf("%s\ngot: '%v', want: '%v'", errMsg, c.MetaData, data.expMetadata) + } + } +} + +func TestGet(t *testing.T) { + datas := []struct { + metadata map[string]string + content string + getKey string + expMetadata map[string]string + expVal string + expErr bool + }{ + { // sync failed + make(map[string]string), "", "", make(map[string]string), "", true, + }, { // get normal key + make(map[string]string), "{\"a\":\"b\"}", "a", map[string]string{"a": "b"}, "b", false, + }, { // key not exist + make(map[string]string), "{\"a\":\"b\"}", "c", map[string]string{"a": "b"}, "", true, + }, { // ignore old client val + map[string]string{"a": "c"}, "{\"a\":\"b\"}", "a", map[string]string{"a": "b"}, "b", false, + }, + } + + for _, data := range datas { + c, _ := NewClient(fakeArtifactDir) + defer os.Remove(c.Path) + ioutil.WriteFile(c.Path, []byte(data.content), 0644) + + val, err := c.Get(data.getKey) + errMsg := fmt.Sprintf("Testing get %q, with metadata: %v, content: %q", + data.getKey, data.metadata, data.content) + if (err == nil && data.expErr) || (err != nil && !data.expErr) { + log.Fatalf("%s\ngot: '%v', want: '%v'", errMsg, err, data.expErr) + } + if !reflect.DeepEqual(c.MetaData, data.expMetadata) { + log.Fatalf("%s\ngot: '%v', want: '%v'", errMsg, c.MetaData, data.expMetadata) + } + if val != data.expVal { + log.Fatalf("%s\ngot: %q, want: %q", errMsg, val, data.expVal) + } + } +} diff --git a/testutils/metahelper/main.go b/testutils/metahelper/main.go new file mode 100644 index 000000000..f6bf4e738 --- /dev/null +++ b/testutils/metahelper/main.go @@ -0,0 +1,66 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "flag" + "fmt" + "log" + + "knative.dev/pkg/testutils/metahelper/client" +) + +var ( + getKeyOpt string + saveKeyOpt string + valOpt string +) + +func main() { + flag.StringVar(&getKeyOpt, "get", "", "get val for a key") + flag.StringVar(&saveKeyOpt, "set", "", "save val for a key, must have --val supplied") + flag.StringVar(&valOpt, "val", "", "val to be modified, only useful when --save is passed") + flag.Parse() + // Create with default path of metahelper/client, so that the path is + // consistent with all other consumers of metahelper/client that run within + // the same context of this tool + c, err := client.NewClient("") + if err != nil { + log.Fatal(err) + } + + var res string + switch { + case getKeyOpt != "" && saveKeyOpt != "": + log.Fatal("--get and --save can't be used at the same time") + case getKeyOpt != "": + gotVal, err := c.Get(getKeyOpt) + if err != nil { + log.Fatalf("Failed getting value for %q from %q: '%v'", getKeyOpt, c.Path, err) + } + res = gotVal + case saveKeyOpt != "": + if valOpt == "" { + log.Fatal("--val must be supplied when using --save") + } + log.Printf("Writing files to %s", c.Path) + if err := c.Set(saveKeyOpt, valOpt); err != nil { + log.Fatalf("Failed saving %q:%q to %q: '%v'", saveKeyOpt, valOpt, c.Path, err) + } + } + fmt.Print(res) +}