From 5cca53a0062bc94d85bb927e8e9c127b081dd986 Mon Sep 17 00:00:00 2001 From: danielpygo Date: Wed, 19 Dec 2018 21:44:19 -0600 Subject: [PATCH] Unit Tests, Travis CI In this commit I added unit tests for the exposed API endpoint methods. I added travis CI, so that it builds each time a new commit . And I have a small fix for minikube as well. Some helper methods and ordering was needed in provenance.go, like a String() change and an ordered_map type. --- .travis.yml | 31 ++ README.md | 10 +- pkg/provenance/provenance.go | 280 +++++++++++++----- pkg/provenance/provenance_test.go | 472 ++++++++++++++++++++++++++++++ pkg/provenance/tests.yaml | 84 ++++++ 5 files changed, 803 insertions(+), 74 deletions(-) create mode 100644 .travis.yml create mode 100644 pkg/provenance/provenance_test.go create mode 100644 pkg/provenance/tests.yaml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2864611 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: go + +#omitting this line fails build bc it tries to use TLS1.0 +# which bitbucket blocks so a clone will not go through +dist: xenial + +# Force-enable Go modules. This will be unnecessary when Go 1.12 lands. +env: + - GO111MODULE=on + +# You don't need to test on very old version of the Go compiler. It's the user's +# responsibility to keep their compilers up to date. +go: + - 1.11.x + +# Only clone the most recent commit. +git: + depth: 1 + +# Skip the install step. Don't `go get` dependencies. Only build with the code +# in vendor/ +install: true + +# email me results if a test fails. +notifications: + recipients: + - ddmoorish@gmail.com + on_success: never + on_failure: always +script: + - go test -v ./... diff --git a/README.md b/README.md index a207f75..bbccdf5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ apt-get install -y gcc make socat git wget
wget https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
-export GOROOT=$PATH:/usr/local/go
+export GOROOT=/usr/local/go
Set up your Go workspace, set the GOPATH to it. This is where all your Go code should be.
mkdir $HOME/goworkspace
@@ -192,7 +192,7 @@ minikube ip -- verify that minikube is up and running
wget https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
-export GOROOT=$PATH:/usr/local/go
+export GOROOT=/usr/local/go
Set up your Go workspace, set the GOPATH to it. This is where all your Go code should be.
mkdir $HOME/goworkspace
@@ -284,6 +284,12 @@ kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgr ``` ![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/bisect.png) + +## Running Unit Tests: + +1. go test -v ./... + + ## Troubleshooting tips: 1) Check that the API server Pod is running: diff --git a/pkg/provenance/provenance.go b/pkg/provenance/provenance.go index 14d553c..9aee0b8 100644 --- a/pkg/provenance/provenance.go +++ b/pkg/provenance/provenance.go @@ -54,6 +54,23 @@ type ProvenanceOfObject struct { Name string } +// Only used when I need to order the AttributeToData map for unit testing +type pair struct { + Attribute string + Data interface{} +} +type OrderedMap []pair + +//Similar to a map access .. +//returns Data, ok +func (o OrderedMap) At(attrib string) (interface{}, bool) { + for _, my_pair := range o { + if my_pair.Attribute == attrib { + return my_pair.Data, true + } + } + return nil, false +} func init() { serviceHost = os.Getenv("KUBERNETES_SERVICE_HOST") servicePort = os.Getenv("KUBERNETES_SERVICE_PORT") @@ -94,7 +111,7 @@ func CollectProvenance() { readKindCompositionFile() useSample := false if onMinikube() { - useSample = true + useSample = true parse(useSample) //using a sample audit log, because //currently audit logging is not supported for minikube } else { @@ -146,19 +163,60 @@ func FindProvenanceObjectByName(name string, allObjects []ProvenanceOfObject) *P return nil } +// This String function must return the same output upon +// different calls. So I am doing ordering and sorting +// here because map is unordered and gives random outputs +// when printing func (s *Spec) String() string { var b strings.Builder fmt.Fprintf(&b, "Version: %d\n", s.Version) - for attribute, data := range s.AttributeToData { - fmt.Fprintf(&b, "%s: %s\n", attribute, data) + + var keys []string + for k, _ := range s.AttributeToData { + keys = append(keys, k) } + sort.Strings(keys) + for _, attribute := range keys { + data := s.AttributeToData[attribute] + integer, ok := data.(int) + if ok { + fmt.Fprintf(&b, " %s: %d\n", attribute, integer) + continue + } + v, ok := data.([]map[string]string) + if ok { + fmt.Fprintf(&b, " %s: [", attribute) + for _, innermap := range v { + fmt.Fprintf(&b, " map[") + var innerkeys []string + for k, _ := range innermap { + innerkeys = append(innerkeys, k) + } + sort.Strings(innerkeys) + var strs []string + for _, k1 := range innerkeys { + strs = append(strs, fmt.Sprintf("%s: %s", k1, innermap[k1])) + } + fmt.Fprintf(&b, strings.Join(strs, " ")) + fmt.Fprintf(&b, "] ") + } + fmt.Fprintf(&b, "]\n") + } else { + fmt.Fprintf(&b, " %s: %s\n", attribute, data) + } + } + return b.String() } +// Returns the string representation of ObjectLineage +// used in GetHistory func (o ObjectLineage) String() string { var b strings.Builder - for version, spec := range o { - fmt.Fprintf(&b, "Version: %d Data: %s\n", version, spec.String()) + specs := getSpecsInOrder(o) + + for _, spec := range specs { + fmt.Fprintf(&b, spec.String()) } return b.String() } @@ -185,30 +243,33 @@ func (o ObjectLineage) GetVersions() string { for _, spec := range specs { outputs = append(outputs, fmt.Sprintf("%s: Version %d", spec.Timestamp, spec.Version)) //cast int to string } - return "[" + strings.Join(outputs, ", \n") + "]\n" + return "[" + strings.Join(outputs, ",\n") + "]\n" } -func (o ObjectLineage) SpecHistory() string { +//https://stackoverflow.com/questions/23330781/sort-go-map-values-by-keys +func (o ObjectLineage) stringInterval(s, e int) string { + var b strings.Builder specs := getSpecsInOrder(o) - specStrings := make([]string, 0) + for _, spec := range specs { - specStrings = append(specStrings, spec.String()) + if spec.Version >= s && spec.Version <= e { + fmt.Fprintf(&b, spec.String()) + } } - return strings.Join(specStrings, "\n") + return b.String() +} +func (o ObjectLineage) SpecHistory() string { + return o.String() } func (o ObjectLineage) SpecHistoryInterval(vNumStart, vNumEnd int) string { - specs := getSpecsInOrder(o) - specStrings := make([]string, 0) - for _, spec := range specs { - if spec.Version >= vNumStart && spec.Version <= vNumEnd { - specStrings = append(specStrings, spec.String()) - } + if vNumStart < 0 { + return "Invalid start parameter" } - return strings.Join(specStrings, "\n") + return o.stringInterval(vNumStart, vNumEnd) } func buildAttributeRelationships(specs []Spec, allQueryPairs [][]string) map[string][][]string { - // A map from top level attribute to array of pairs (represented as 2 len array) + // Returns a map from top level attribute to array of pairs (represented as 2 len array) // mapRelationships with one top level object users, looks like this: // ex: map[users:[[username pallavi] [password pass123]]] mapRelationships := make(map[string][][]string, 0) @@ -290,8 +351,9 @@ func buildQueryPairsSlice(queryArgMap map[string]string) ([][]string, error) { //field value pairs, searched based on the type. func (o ObjectLineage) Bisect(argMap map[string]string) string { specs := getSpecsInOrder(o) - allQueryPairs, err := buildQueryPairsSlice(argMap) + fmt.Printf("Query Attributes Map: %v\n", allQueryPairs) + if err != nil { return err.Error() } @@ -303,7 +365,7 @@ func (o ObjectLineage) Bisect(argMap map[string]string) string { // ask if username ever is daniel and password is ever 223843, because // it could find that in different parts of the spec. They both must be satisfied in the same map object mapRelationships := buildAttributeRelationships(specs, allQueryPairs) - fmt.Printf("Query Attributes Map:%v\n", mapRelationships) + fmt.Printf("Query Attributes Same-parent-relationships: %v\n", mapRelationships) // fmt.Println(specs) for _, spec := range specs { @@ -347,7 +409,7 @@ func (o ObjectLineage) Bisect(argMap map[string]string) string { } andGate = append(andGate, satisfied) } - fmt.Printf("Result of checking all attributes:%v\n", andGate) + // fmt.Printf("Result of checking all attributes: %v\n", andGate) allTrue := all(andGate) //all indexes in andGate must be true if allTrue { @@ -357,7 +419,7 @@ func (o ObjectLineage) Bisect(argMap map[string]string) string { return "No version found that matches the query." } -//this is for a field like deploymentName where the underyling state or data is a string +//this is for a field like deploymentName where the underlying state or data is a string func handleTrivialFields(qkey, qval, mkey, fieldData string) bool { if qkey == mkey && qval == fieldData { return true @@ -377,6 +439,22 @@ func handleSimpleFields(vStringSlice []string, qkey, qval, mkey string) bool { return satisfied } func handleCompositeFields(vSliceMap []map[string]string, mapRelationships map[string][][]string, qkey, qval, mkey string) bool { + isPossible := false + for _, m := range vSliceMap { + for k, _ := range m { + if k == qkey { + isPossible = true + } + } + } + if !isPossible { + //for example databases qkey will never pass a + //composite field test since it is never a key under + //the map vSliceMap + // fmt.Printf("NOT POSSIBLE, %s %s\n", qkey, mkey) + return false + } + for _, mymap := range vSliceMap { //looping through each elem in the mapSlice // check if there is any // other necessary requirements for this mkey. @@ -392,7 +470,6 @@ func handleCompositeFields(vSliceMap []map[string]string, mapRelationships map[s // find all the related attributes associated with the top-level specs // attribute mkey. this would be users for the postgres crd example. attributeCombinedQuery, ok := mapRelationships[mkey] - if ok { //ensure multiple attributes are jointly satisfied jointQueryResults := make([]bool, 0) //jointQueryResults is a boolean slice that represents the satisfiability @@ -484,69 +561,126 @@ func compareMaps(mapSlice1, mapSlice2 []map[string]string) bool { } return all(foundMatches) } + +//Need some way to bring order to the elements of the AttributeToData map, +// because otherwise, the output is randomly ordered and I cannot unit test that. +// so This method orders the map based on the Attribute key and is similar +// to C++'s pair. +func (s Spec) OrderedPairs() OrderedMap { + var keys []string + for k, _ := range s.AttributeToData { + keys = append(keys, k) + } + sort.Strings(keys) + orderedRet := make(OrderedMap, 0) + for _, k1 := range keys { + p := pair{} + p.Attribute = k1 + p.Data = s.AttributeToData[k1] + orderedRet = append(orderedRet, p) + } + return orderedRet +} func (o ObjectLineage) FullDiff(vNumStart, vNumEnd int) string { var b strings.Builder - sp1 := o[vNumStart] - sp2 := o[vNumEnd] - for attribute, data1 := range sp1.AttributeToData { - data2, ok := sp2.AttributeToData[attribute] //check if the attribute even exists + sp1 := o[vNumStart].OrderedPairs() + sp2 := o[vNumEnd].OrderedPairs() + for _, my_pair := range sp1 { + attr := my_pair.Attribute + data1 := my_pair.Data + data2, ok := sp2.At(attr) //check if the attribute even exists if ok { - getDiff(&b, attribute, data1, data2, vNumStart, vNumEnd) + getDiff(&b, attr, data1, data2, vNumStart, vNumEnd) } else { //for the case where a key exists in spec 1 that doesn't exist in spec 2 - fmt.Fprintf(&b, "Found diff on attribute %s:\n", attribute) - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, data1) - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, "No attribute found.") + fmt.Fprintf(&b, "Found diff on attribute %s:\n", attr) + fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, data1) + fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, "No attribute found.") } } //for the case where a key exists in spec 2 that doesn't exist in spec 1 - for attribute, data1 := range sp2.AttributeToData { - if _, ok := sp2.AttributeToData[attribute]; !ok { - fmt.Fprintf(&b, "Found diff on attribute %s:\n", attribute) - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, "No attribute found.") - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, data1) + for _, my_pair := range sp2 { + attr := my_pair.Attribute + data1 := my_pair.Data + if _, ok := sp2.At(attr); !ok { + fmt.Fprintf(&b, "Found diff on attribute %s:\n", attr) + fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, "No attribute found.") + fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, data1) } } return b.String() } +func orderInnerMaps(m []map[string]string) []OrderedMap { + orderedMaps := make([]OrderedMap, 0) + for _, mi := range m { + var keys []string + for k, _ := range mi { + keys = append(keys, k) + } + sort.Strings(keys) + orderedRet := make(OrderedMap, 0) + for _, k1 := range keys { + p := pair{} + p.Attribute = k1 + p.Data = mi[k1] + orderedRet = append(orderedRet, p) + } + orderedMaps = append(orderedMaps, orderedRet) + } + + return orderedMaps +} func getDiff(b *strings.Builder, fieldName string, data1, data2 interface{}, vNumStart, vNumEnd int) string { str1, ok1 := data1.(string) str2, ok2 := data2.(string) if ok1 && ok2 && str1 != str2 { fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, data1) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, data2) + fmt.Fprintf(b, " Version %d: %s\n", vNumStart, data1) + fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, data2) } int1, ok1 := data1.(int) int2, ok2 := data2.(int) if ok1 && ok2 && int1 != int2 { fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, data1) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, data2) + fmt.Fprintf(b, " Version %d: %d\n", vNumStart, int1) + fmt.Fprintf(b, " Version %d: %d\n", vNumEnd, int2) } strArray1, ok1 := data1.([]string) strArray2, ok2 := data2.([]string) if ok1 && ok2 { - for _, str := range strArray1 { - found := false - for _, val := range strArray2 { - if str == val { - found = true + sort.Strings(strArray1) + sort.Strings(strArray2) + + //When the arrays are not the same len, found a difference. + if len(strArray1) != len(strArray2) { + fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName) + fmt.Fprintf(b, " Version %d: %s\n", vNumStart, strArray1) + fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, strArray2) + } else { + for _, val1 := range strArray1 { + found := false + for _, val2 := range strArray2 { + if val1 == val2 { + found = true + } + } + if !found { // if an element does not have a match in the next version + fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName) + fmt.Fprintf(b, " Version %d: %s\n", vNumStart, strArray1) + fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, strArray2) + break } - } - if !found { // if an element does not have a match in the next version - fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, strArray1) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, strArray2) } } } strMap1, ok1 := data1.([]map[string]string) strMap2, ok2 := data2.([]map[string]string) if ok1 && ok2 { + orderedInnerMap1 := orderInnerMaps(strMap1) + orderedInnerMap2 := orderInnerMaps(strMap2) if !compareMaps(strMap1, strMap2) { fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, strMap1) - fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, strMap2) + fmt.Fprintf(b, " Version %d: %s\n", vNumStart, orderedInnerMap1) + fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, orderedInnerMap2) } } @@ -554,6 +688,8 @@ func getDiff(b *strings.Builder, fieldName string, data1, data2 interface{}, vNu } func (o ObjectLineage) FieldDiff(fieldName string, vNumStart, vNumEnd int) string { var b strings.Builder + //Since this is a single field, do not have to do the OrderedMap business like the FullDiff. + //Same outp everytime data1, ok1 := o[vNumStart].AttributeToData[fieldName] data2, ok2 := o[vNumEnd].AttributeToData[fieldName] switch { @@ -561,12 +697,12 @@ func (o ObjectLineage) FieldDiff(fieldName string, vNumStart, vNumEnd int) strin return getDiff(&b, fieldName, data1, data2, vNumStart, vNumEnd) case !ok1 && ok2: fmt.Fprintf(&b, "Found diff on attribute %s:\n", fieldName) - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, "No attribute found.") - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, data2) + fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, "No attribute found.") + fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, data2) case ok1 && !ok2: fmt.Fprintf(&b, "Found diff on attribute %s:\n", fieldName) - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, data1) - fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, "No attribute found.") + fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, data1) + fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, "No attribute found.") case !ok1 && !ok2: fmt.Fprintf(&b, "Attribute not found in either version %d or %d", vNumStart, vNumEnd) } @@ -576,24 +712,24 @@ func (o ObjectLineage) FieldDiff(fieldName string, vNumStart, vNumEnd int) strin //Ref:https://www.sohamkamani.com/blog/2017/10/18/parsing-json-in-golang/#unstructured-data func parse(useSample bool) { - var log *os.File + var log *os.File var err error - if useSample { - log, err = os.Open("/tmp/minikube-sample-audit.log") - if err != nil { - fmt.Println(fmt.Sprintf("could not open the log file %s", err)) - panic(err) - } + if useSample { + log, err = os.Open("/tmp/minikube-sample-audit.log") + if err != nil { + fmt.Println(fmt.Sprintf("could not open the log file %s", err)) + panic(err) + } } else { - if _, err := os.Stat("/tmp/kube-apiserver-audit.log"); os.IsNotExist(err) { - fmt.Println(fmt.Sprintf("could not stat the path %s", err)) - panic(err) - } - log, err = os.Open("/tmp/kube-apiserver-audit.log") - if err != nil { - fmt.Println(fmt.Sprintf("could not open the log file %s", err)) - panic(err) - } + if _, err := os.Stat("/tmp/kube-apiserver-audit.log"); os.IsNotExist(err) { + fmt.Println(fmt.Sprintf("could not stat the path %s", err)) + panic(err) + } + log, err = os.Open("/tmp/kube-apiserver-audit.log") + if err != nil { + fmt.Println(fmt.Sprintf("could not open the log file %s", err)) + panic(err) + } } defer log.Close() diff --git a/pkg/provenance/provenance_test.go b/pkg/provenance/provenance_test.go new file mode 100644 index 0000000..10b6dcb --- /dev/null +++ b/pkg/provenance/provenance_test.go @@ -0,0 +1,472 @@ +package provenance + +import ( + "fmt" + "io/ioutil" + "log" + "testing" + // "time" + + "github.com/jinzhu/copier" + yaml "gopkg.in/yaml.v2" +) + +//https://stackoverflow.com/questions/30947534/reading-a-yaml-file-in-golang +type conf struct { + HistoryTest1 string `yaml:"HistoryTest1"` + HistoryIntervalTest1 string `yaml:"HistoryIntervalTest1"` + TestFullDiff1 string `yaml:"TestFullDiff1"` + TestFullDiff2 string `yaml:"TestFullDiff2"` + TestFullDiff3 string `yaml:"TestFullDiff3"` + TestFieldDiff1 string `yaml:"TestFieldDiff1"` + TestFieldDiff2 string `yaml:"TestFieldDiff2"` + TestFieldDiff3 string `yaml:"TestFieldDiff3"` + TestGetVersion1 string `yaml:"TestGetVersion1"` +} + +// Parses an expected-output file 'conf' into some fields, +// which will be used during Unit tests +func (c *conf) getConf() *conf { + + yamlFile, err := ioutil.ReadFile("tests.yaml") + if err != nil { + log.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, c) + if err != nil { + log.Fatalf("Unmarshal: %v", err) + } + return c +} + +type Args struct { + Version int + DeploymentName string + Image string + Replicas int + Users []map[string]string + Databases []string +} + +func addUser(users []map[string]string, user, pass string) []map[string]string { + + s := make(map[string]string, 0) + s["username"] = user + s["password"] = pass + users = append(users, s) + return users +} +func updateUser(users []map[string]string, user, newPass string) []map[string]string { + copied := make([]map[string]string, len(users)) + copy(copied, users) + for _, u := range copied { + found := u["username"] == user + if found { + u["password"] = newPass + } + } + return copied +} +func addDatabase(databases []string, database string) []string { + for _, d := range databases { + //already exists + if database == d { + return databases + } + } + + databases = append(databases, database) + return databases +} +func removeDatabase(databases []string, database string) []string { + + for i, d := range databases { + if d == database { + return append(databases[:i], databases[i+1:]...) + } + } + //did not find the database to remove + return databases +} + +func makeSpec(a Args) Spec { + attributeToData := make(map[string]interface{}, 0) + attributeToData["deploymentName"] = a.DeploymentName + attributeToData["image"] = a.Image + attributeToData["replicas"] = a.Replicas + attributeToData["users"] = a.Users + attributeToData["databases"] = a.Databases + s := Spec{} + s.Version = a.Version + // Using a filler date for unit testing + s.Timestamp = "2006-01-02 15:04:05" + s.AttributeToData = attributeToData + return s +} + +// Initializes an Args with some data +func initArgs() Args { + ds := make([]string, 0) + ds = append(ds, "servers") + ds = append(ds, "staff") + + users := make([]map[string]string, 0) + u1 := make(map[string]string, 0) + u1["username"] = "daniel" + u1["password"] = "4557832+#^" + + u2 := make(map[string]string, 0) + u2["username"] = "kulkarni" + u2["password"] = "5*7832!@$" + + users = append(users, u1) + users = append(users, u2) + a := Args{} + a.Users = users + a.Databases = ds + a.Image = "postgres:9.3" + a.Replicas = 3 + a.Version = 1 + a.DeploymentName = "MyDeployment" + + return a +} + +// This method is meant to deep copy the Args datastructure. +// copier deep copies fields that are simple, but I had +// to deep copy maps and slices myself. +func deepCopyArgs(arg Args) Args { + nArgs := Args{} + err := copier.Copy(&nArgs, &arg) + if err != nil { + fmt.Printf("Error doing a deep copy of Args: %s", err) + return nArgs + } + + //deep copying for maps, + // tried two popular libraries and they didn't work.. + newUsers := make([]map[string]string, 0) + for _, m := range nArgs.Users { + u := make(map[string]string, 0) + for k, v := range m { + u[k] = v + } + newUsers = append(newUsers, u) + + } + newDb := make([]string, 0) + for _, m := range nArgs.Databases { + newDb = append(newDb, m) + } + //The above code successfully made a deep copy of the map + nArgs.Users = newUsers + nArgs.Databases = newDb + return nArgs +} + +//This method builds some lineage data +func buildLineage() (ObjectLineage, Args) { + objectLineage := make(map[int]Spec) + args := initArgs() + spec1 := makeSpec(args) + objectLineage[spec1.Version] = spec1 + + args = deepCopyArgs(args) + users2 := addUser(args.Users, "johnson", "fluffy23") + args.Users = users2 + args.Version = 2 + spec2 := makeSpec(args) + objectLineage[spec2.Version] = spec2 + + args = deepCopyArgs(args) + users3 := addUser(args.Users, "thomas", "bedsheet85") + args.Users = users3 + args.Version = 3 + spec3 := makeSpec(args) + objectLineage[spec3.Version] = spec3 + + args = deepCopyArgs(args) + db1 := addDatabase(args.Databases, "logging") + args.Databases = db1 + args.Version = 4 + spec4 := makeSpec(args) + objectLineage[spec4.Version] = spec4 + + args = deepCopyArgs(args) + db2 := addDatabase(args.Databases, "demographics") + args.Databases = db2 + args.Version = 5 + spec5 := makeSpec(args) + objectLineage[spec5.Version] = spec5 + args = deepCopyArgs(args) + return objectLineage, args +} + +// This method is testing that the joint functionality of Bisect works +// with a complex query involving a complex query of 3 fields +func TestBisectGeneral(t *testing.T) { + objLineage, _ := buildLineage() + argMapTest := make(map[string]string, 0) + argMapTest["field1"] = "databases" + argMapTest["value1"] = "demographics" + argMapTest["field2"] = "username" + argMapTest["value2"] = "daniel" + argMapTest["field3"] = "password" + argMapTest["value3"] = "4557832+#^" + + vOutput := objLineage.Bisect(argMapTest) + expected := "Version: 5" + if vOutput != expected { + t.Errorf("Version output for TestBisectGeneral() was incorrect, got: %s, want: %s.\n", vOutput, expected) + } +} + +// This method changes a user's password, and is testing +// whether the bisect will take a query with the same +// parent element (user), and process a complicated +// query involving maps. +func TestBisectPasswordChanged(t *testing.T) { + objLineage, newArgs := buildLineage() + + users := updateUser(newArgs.Users, "daniel", "JEK873BUL!") + newArgs.Users = users + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + argMapTest := make(map[string]string, 0) + argMapTest["field1"] = "username" + argMapTest["value1"] = "daniel" + argMapTest["field2"] = "password" + argMapTest["value2"] = "JEK873BUL!" + + vOutput := objLineage.Bisect(argMapTest) + expected := "Version: 6" + if vOutput != expected { + t.Errorf("Version output for TestBisectPasswordChanged() was incorrect, got: %s, want: %s.\n", vOutput, expected) + } +} + +// This method adds a new databases, admins, and +// is testing Bisect on a slice type. +func TestBisectAddDatabase(t *testing.T) { + objLineage, newArgs := buildLineage() + + db := addDatabase(newArgs.Databases, "admins") + newArgs.Databases = db + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + + argMapTest := make(map[string]string, 0) + argMapTest["field1"] = "databases" + argMapTest["value1"] = "admins" + + vOutput := objLineage.Bisect(argMapTest) + expected := "Version: 6" + if vOutput != expected { + t.Errorf("Version output for TestBisectAddDatabase() was incorrect, got: %s, want: %s.", vOutput, expected) + } +} + +// This method adds a new user, Stevejobs, and +// ensures that a bisect query will see this. +func TestBisectAddUser(t *testing.T) { + objLineage, newArgs := buildLineage() + users := addUser(newArgs.Users, "Stevejobs", "apple468") + newArgs.Users = users + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + + argMapTest := make(map[string]string, 0) + argMapTest["field1"] = "username" + argMapTest["value1"] = "Stevejobs" + argMapTest["field2"] = "password" + argMapTest["value2"] = "apple468" + + vOutput := objLineage.Bisect(argMapTest) + expected := "Version: 6" + if vOutput != expected { + t.Errorf("Version output for TestBisectAddUser() was incorrect, got: %s, want: %s.", vOutput, expected) + } +} + +// This Unit test changes a string field and ensures that the Bisect +// will pick up this change. +func TestBisectChangeDeploymentName(t *testing.T) { + objLineage, newArgs := buildLineage() + newArgs.DeploymentName = "Deployment66" + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + + argMapTest := make(map[string]string, 0) + argMapTest["field1"] = "deploymentName" + argMapTest["value1"] = "Deployment66" + + vOutput := objLineage.Bisect(argMapTest) + expected := "Version: 6" + if vOutput != expected { + t.Errorf("Version output for TestBisectChangeDeploymentName() was incorrect, got: %s, want: %s.\n", vOutput, expected) + } +} +func TestHistory(t *testing.T) { + objLineage, _ := buildLineage() + + vOutput := objLineage.SpecHistory() + var c conf + c.getConf() + expected := c.HistoryTest1 + + if vOutput != expected { + t.Errorf("History output for TestHistory() was incorrect, got: %s, want: %s.\n", vOutput, expected) + } +} + +func TestHistoryInterval(t *testing.T) { + objLineage, _ := buildLineage() + + vOutput := objLineage.SpecHistoryInterval(2, 4) + var c conf + c.getConf() + expected := c.HistoryIntervalTest1 + + if vOutput != expected { + t.Errorf("History output for TestHistoryInterval() was incorrect, got: %s, want: %s.\n", vOutput, expected) + } +} + +//Tests changes made to ordinary string literals +func TestFieldDiff1(t *testing.T) { + objLineage, newArgs := buildLineage() + newArgs.DeploymentName = "Deployment66" + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + output := objLineage.FieldDiff("deploymentName", 5, 6) + var c conf + c.getConf() + expected := c.TestFieldDiff1 + + if output != expected { + t.Errorf("Diff output for TestFieldDiff1() was incorrect, got: %s, want: %s.\n", output, expected) + } +} + +// Tests the edge case where an element in a slice has changed and the slices are +// the same length. +func TestFieldDiff2(t *testing.T) { + objLineage, newArgs := buildLineage() + databasest := removeDatabase(newArgs.Databases, "demographics") + databases := addDatabase(databasest, "testing") + + newArgs.Databases = databases + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + output := objLineage.FieldDiff("databases", 5, 6) + + var c conf + c.getConf() + expected := c.TestFieldDiff2 + if output != expected { + t.Errorf("Diff output for TestFieldDiff2() was incorrect, got: %s, want: %s.\n", output, expected) + } +} + +//Testing diff changes for an ordinary int alteration +func TestFieldDiff3(t *testing.T) { + objLineage, newArgs := buildLineage() + + newArgs.Replicas = 10 + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + output := objLineage.FieldDiff("replicas", 5, 6) + + var c conf + c.getConf() + expected := c.TestFieldDiff3 + if output != expected { + t.Errorf("Diff output for TestFieldDiff3() was incorrect, got: %s, want: %s.\n", output, expected) + } +} + +// Tests the Diff output for altering a string field +func TestFullDiff1(t *testing.T) { + objLineage, newArgs := buildLineage() + newArgs.DeploymentName = "Deployment66" + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + + output := objLineage.FullDiff(5, 6) + var c conf + c.getConf() + expected := c.TestFullDiff1 + + if output != expected { + t.Errorf("Diff output for TestFullDiff1() was incorrect, got: %s, want: %s.\n", output, expected) + } +} + +// Tests Diff functionality for when a user is added. +func TestFullDiff2(t *testing.T) { + objLineage, newArgs := buildLineage() + users := addUser(newArgs.Users, "Stevejobs", "apple468") + newArgs.Users = users + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + + output := objLineage.FullDiff(5, 6) + var c conf + c.getConf() + expected := c.TestFullDiff2 + + if output != expected { + t.Errorf("Diff output for TestFullDiff2() was incorrect, got: %s, want: %s.\n", output, expected) + } +} + +// This method tests FullDiff, by making a combination of changes on a custom resource +// In doing so, tests the edge case where slices (such as Databases) is not equal +func TestFullDiff3(t *testing.T) { + objLineage, newArgs := buildLineage() + users := addUser(newArgs.Users, "Stevejobs", "apple468") + databases := addDatabase(newArgs.Databases, "testing") + + newArgs.Users = users + newArgs.Databases = databases + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + output := objLineage.FullDiff(5, 6) + + var c conf + c.getConf() + expected := c.TestFullDiff3 + + if output != expected { + t.Errorf("Diff output for TestFullDiff3() was incorrect, got: %s, want: %s.\n", output, expected) + } +} + +// Tests GetVersions for when a new version is added +func TestGetVersion1(t *testing.T) { + objLineage, newArgs := buildLineage() + databases := addDatabase(newArgs.Databases, "testing") + + newArgs.Databases = databases + newArgs.Version = 6 + spec6 := makeSpec(newArgs) + objLineage[spec6.Version] = spec6 + output := objLineage.GetVersions() + var c conf + c.getConf() + expected := c.TestGetVersion1 + + if output != expected { + t.Errorf("Versions output for TestFullDiff3() was incorrect, got: %s, want: %s.\n", output, expected) + } +} diff --git a/pkg/provenance/tests.yaml b/pkg/provenance/tests.yaml new file mode 100644 index 0000000..cc3ed4b --- /dev/null +++ b/pkg/provenance/tests.yaml @@ -0,0 +1,84 @@ +HistoryTest1: | + Version: 1 + databases: [servers staff] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] ] + Version: 2 + databases: [servers staff] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] ] + Version: 3 + databases: [servers staff] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ] + Version: 4 + databases: [servers staff logging] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ] + Version: 5 + databases: [servers staff logging demographics] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ] +HistoryIntervalTest1: | + Version: 2 + databases: [servers staff] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] ] + Version: 3 + databases: [servers staff] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ] + Version: 4 + databases: [servers staff logging] + deploymentName: MyDeployment + image: postgres:9.3 + replicas: 3 + users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ] +TestFullDiff1: | + Found diff on attribute deploymentName: + Version 5: MyDeployment + Version 6: Deployment66 +TestFieldDiff1: | + Found diff on attribute deploymentName: + Version 5: MyDeployment + Version 6: Deployment66 +TestFieldDiff2: | + Found diff on attribute databases: + Version 5: [demographics logging servers staff] + Version 6: [logging servers staff testing] +TestFieldDiff3: | + Found diff on attribute replicas: + Version 5: 3 + Version 6: 10 +TestFullDiff2: | + Found diff on attribute users: + Version 5: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}]] + Version 6: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}] [{password apple468} {username Stevejobs}]] +TestFullDiff3: | + Found diff on attribute databases: + Version 5: [demographics logging servers staff] + Version 6: [demographics logging servers staff testing] + Found diff on attribute users: + Version 5: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}]] + Version 6: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}] [{password apple468} {username Stevejobs}]] +TestGetVersion1: | + [2006-01-02 15:04:05: Version 1, + 2006-01-02 15:04:05: Version 2, + 2006-01-02 15:04:05: Version 3, + 2006-01-02 15:04:05: Version 4, + 2006-01-02 15:04:05: Version 5, + 2006-01-02 15:04:05: Version 6]