From e45254685133244d852d52be6e8720a86b5d9a79 Mon Sep 17 00:00:00 2001 From: Brian Giori Date: Thu, 3 Aug 2023 11:19:15 -0700 Subject: [PATCH 1/3] evaluate specific flags, topo sort before evaluate --- pkg/experiment/local/client.go | 54 +++- pkg/experiment/local/sort.go | 88 ++++++ pkg/experiment/local/sort_test.go | 435 ++++++++++++++++++++++++++++++ 3 files changed, 564 insertions(+), 13 deletions(-) create mode 100644 pkg/experiment/local/sort.go create mode 100644 pkg/experiment/local/sort_test.go diff --git a/pkg/experiment/local/client.go b/pkg/experiment/local/client.go index 7342b9e..137dea0 100644 --- a/pkg/experiment/local/client.go +++ b/pkg/experiment/local/client.go @@ -25,7 +25,7 @@ type Client struct { config *Config client *http.Client poller *poller - flags *string + flags map[string]interface{} } func Initialize(apiKey string, config *Config) *Client { @@ -62,25 +62,29 @@ func (c *Client) Start() error { } c.flags = result }) - return nil } func (c *Client) Evaluate(user *experiment.User, flagKeys []string) (map[string]experiment.Variant, error) { variants := make(map[string]experiment.Variant) - if len(*c.flags) == 0 { + if len(c.flags) == 0 { c.log.Debug("evaluate: no flags") return variants, nil - } userJson, err := json.Marshal(user) if err != nil { return nil, err } - - c.log.Debug("evaluate:\n\t- user: %v\n\t- rules: %v\n", string(userJson), *c.flags) - - resultJson := evaluation.Evaluate(*c.flags, string(userJson)) + sortedFlags, err := topologicalSort(c.flags, flagKeys) + if err != nil { + return nil, err + } + flagsJson, err := json.Marshal(sortedFlags) + if err != nil { + return nil, err + } + c.log.Debug("evaluate:\n\t- user: %v\n\t- rules: %v\n", string(userJson), string(flagsJson)) + resultJson := evaluation.Evaluate(string(flagsJson), string(userJson)) c.log.Debug("evaluate result: %v\n", resultJson) var interopResult *interopResult err = json.Unmarshal([]byte(resultJson), &interopResult) @@ -149,10 +153,19 @@ func (c *Client) doRules() (map[string]interface{}, error) { } func (c *Client) Flags() (*string, error) { - return c.doFlags() + flags, err := c.doFlags() + if err != nil { + return nil, err + } + flagsJson, err := json.Marshal(flags) + if err != nil { + return nil, err + } + flagsString := string(flagsJson) + return &flagsString, nil } -func (c *Client) doFlags() (*string, error) { +func (c *Client) doFlags() (map[string]interface{}, error) { endpoint, err := url.Parse(c.config.ServerUrl) if err != nil { return nil, err @@ -177,9 +190,24 @@ func (c *Client) doFlags() (*string, error) { if err != nil { return nil, err } - flags := string(body) - c.log.Debug("flags: %v", flags) - return &flags, nil + c.log.Debug("flags: %v", string(body)) + flagsArray := make([]interface{}, 0) + err = json.Unmarshal(body, &flagsArray) + if err != nil { + return nil, err + } + // Extract keys and create flags map + flags := make(map[string]interface{}) + for _, flagAny := range flagsArray { + switch flag := flagAny.(type) { + case map[string]interface{}: + switch flagKey := flag["flagKey"].(type) { + case string: + flags[flagKey] = flag + } + } + } + return flags, nil } func contains(s []string, e string) bool { diff --git a/pkg/experiment/local/sort.go b/pkg/experiment/local/sort.go new file mode 100644 index 0000000..a8f9dc8 --- /dev/null +++ b/pkg/experiment/local/sort.go @@ -0,0 +1,88 @@ +package local + +import "fmt" + +func topologicalSort(flags map[string]interface{}, flagKeys []string) ([]interface{}, error) { + result := make([]interface{}, 0) + // Extract keys and copy flags map + keys := make([]string, 0) + available := make(map[string]interface{}) + for k, v := range flags { + keys = append(keys, k) + available[k] = v + } + // Get the starting keys + startingKeys := make([]string, 0) + if len(flagKeys) > 0 { + startingKeys = flagKeys + } else { + startingKeys = keys + } + // Sort into result + for _, flagKey := range startingKeys { + traversal, err := parentTraversal(flagKey, available, []string{}) + if err != nil { + return nil, err + } + if len(traversal) > 0 { + result = append(result, traversal...) + } + } + return result, nil +} + +func parentTraversal(flagKey string, available map[string]interface{}, path []string) ([]interface{}, error) { + flag := available[flagKey] + if flag == nil { + return nil, nil + } + dependencies := extractDependencies(flag) + if len(dependencies) == 0 { + delete(available, flagKey) + return []interface{}{flag}, nil + } + path = append(path, flagKey) + result := make([]interface{}, 0) + for _, parentKey := range dependencies { + if contains(path, parentKey) { + return nil, fmt.Errorf("detected a cycle between flags %v", path) + } + traversal, err := parentTraversal(parentKey, available, path) + if err != nil { + return nil, err + } + if len(traversal) > 0 { + result = append(result, traversal...) + } + } + result = append(result, flag) + path = path[:len(path)-1] + delete(available, flagKey) + return result, nil +} + +func extractDependencies(flag interface{}) []string { + switch f := flag.(type) { + case map[string]interface{}: + parentDependenciesAny := f["parentDependencies"] + if parentDependenciesAny == nil { + return nil + } + switch parentDependencies := parentDependenciesAny.(type) { + case map[string]interface{}: + flagsAny := parentDependencies["flags"] + if flagsAny == nil { + return nil + } + switch flags := flagsAny.(type) { + case map[string]interface{}: + result := make([]string, 0) + for k, _ := range flags { + result = append(result, k) + } + return result + } + } + } + return nil +} diff --git a/pkg/experiment/local/sort_test.go b/pkg/experiment/local/sort_test.go new file mode 100644 index 0000000..4f4d6da --- /dev/null +++ b/pkg/experiment/local/sort_test.go @@ -0,0 +1,435 @@ +package local + +import ( + "reflect" + "testing" +) + +func TestEmpty(t *testing.T) { + // No flag keys + { + inputFlags := flagsArray() + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag keys + { + inputFlags := flagsArray() + inputFlagKeys := []string{"1"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } +} + +func TestSingleFlagNoDependencies(t *testing.T) { + // No flag keys + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{}}) + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray(flag{Key: "1", Dependencies: []string{}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag keys + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{}}) + inputFlagKeys := []string{"1"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray(flag{Key: "1", Dependencies: []string{}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag no match + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{}}) + inputFlagKeys := []string{"999"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } +} + +func TestSingleFlagWithDependencies(t *testing.T) { + // No flag keys + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{"2"}}) + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray(flag{Key: "1", Dependencies: []string{"2"}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag keys + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{"2"}}) + inputFlagKeys := []string{"1"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray(flag{Key: "1", Dependencies: []string{"2"}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag no match + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{"2"}}) + inputFlagKeys := []string{"999"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } +} +func TestMultipleFlagsNoDependencies(t *testing.T) { + // No flag keys + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{}}, + flag{Key: "2", Dependencies: []string{}}) + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "1", Dependencies: []string{}}, + flag{Key: "2", Dependencies: []string{}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag keys + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{}}, + flag{Key: "2", Dependencies: []string{}}) + inputFlagKeys := []string{"1", "2"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "1", Dependencies: []string{}}, + flag{Key: "2", Dependencies: []string{}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag no match + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{}}, + flag{Key: "2", Dependencies: []string{}}) + inputFlagKeys := []string{"99", "999"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } +} +func TestMultipleFlagWithDependencies(t *testing.T) { + // No flag keys + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"2"}}, + flag{Key: "2", Dependencies: []string{"3"}}, + flag{Key: "3", Dependencies: []string{}}) + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "3", Dependencies: []string{}}, + flag{Key: "2", Dependencies: []string{"3"}}, + flag{Key: "1", Dependencies: []string{"2"}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag keys + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"2"}}, + flag{Key: "2", Dependencies: []string{"3"}}, + flag{Key: "3", Dependencies: []string{}}) + inputFlagKeys := []string{"1", "2"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "3", Dependencies: []string{}}, + flag{Key: "2", Dependencies: []string{"3"}}, + flag{Key: "1", Dependencies: []string{"2"}}) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } + // With flag no match + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"2"}}, + flag{Key: "2", Dependencies: []string{"3"}}, + flag{Key: "3", Dependencies: []string{}}) + inputFlagKeys := []string{"999"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } +} +func TestSingleFlagCycle(t *testing.T) { + // No flag keys + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{"1"}}) + inputFlagKeys := make([]string, 0) + _, err := topologicalSortArray(inputFlags, inputFlagKeys) + if err == nil { + t.Fatalf("expected cycle error") + } + } + // With flag keys + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{"1"}}) + inputFlagKeys := []string{"1"} + _, err := topologicalSortArray(inputFlags, inputFlagKeys) + if err == nil { + t.Fatalf("expected cycle error") + } + + } + // With flag no match + { + inputFlags := flagsArray(flag{Key: "1", Dependencies: []string{"1"}}) + inputFlagKeys := []string{"999"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } +} +func TestTwoFlagCycle(t *testing.T) { + // No flag keys + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"2"}}, + flag{Key: "2", Dependencies: []string{"1"}}) + inputFlagKeys := make([]string, 0) + _, err := topologicalSortArray(inputFlags, inputFlagKeys) + if err == nil { + t.Fatalf("expected cycle error") + } + } + // With flag keys + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"2"}}, + flag{Key: "2", Dependencies: []string{"1"}}) + inputFlagKeys := []string{"2"} + _, err := topologicalSortArray(inputFlags, inputFlagKeys) + if err == nil { + t.Fatalf("expected cycle error") + } + } + // With flag no match + { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"2"}}, + flag{Key: "2", Dependencies: []string{"1"}}) + inputFlagKeys := []string{"999"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray() + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } + } +} +func TestMultipleFlagsComplexCycle(t *testing.T) { + inputFlags := flagsArray( + flag{Key: "3", Dependencies: []string{"1", "2"}}, + flag{Key: "1", Dependencies: []string{}}, + flag{Key: "4", Dependencies: []string{"21", "3"}}, + flag{Key: "2", Dependencies: []string{}}, + flag{Key: "5", Dependencies: []string{"3"}}, + flag{Key: "6", Dependencies: []string{}}, + flag{Key: "7", Dependencies: []string{}}, + flag{Key: "8", Dependencies: []string{"9"}}, + flag{Key: "9", Dependencies: []string{}}, + flag{Key: "20", Dependencies: []string{"4"}}, + flag{Key: "21", Dependencies: []string{"20"}}, + ) + inputFlagKeys := make([]string, 0) + _, err := topologicalSortArray(inputFlags, inputFlagKeys) + if err == nil { + t.Fatalf("expected cycle error") + } +} +func TestComplexNoCycleStartingWithLeaf(t *testing.T) { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"6", "3"}}, + flag{Key: "2", Dependencies: []string{"8", "5", "3", "1"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "9", Dependencies: []string{"10", "7", "5"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "20", Dependencies: []string{}}, + flag{Key: "21", Dependencies: []string{"20"}}, + flag{Key: "30", Dependencies: []string{}}, + ) + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "1", Dependencies: []string{"6", "3"}}, + flag{Key: "2", Dependencies: []string{"8", "5", "3", "1"}}, + flag{Key: "9", Dependencies: []string{"10", "7", "5"}}, + flag{Key: "20", Dependencies: []string{}}, + flag{Key: "21", Dependencies: []string{"20"}}, + flag{Key: "30", Dependencies: []string{}}, + ) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } +} +func TestComplexNoCycleStartingWithMiddle(t *testing.T) { + inputFlags := flagsArray( + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "1", Dependencies: []string{"6", "3"}}, + flag{Key: "2", Dependencies: []string{"8", "5", "3", "1"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "9", Dependencies: []string{"10", "7", "5"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "20", Dependencies: []string{}}, + flag{Key: "21", Dependencies: []string{"20"}}, + flag{Key: "30", Dependencies: []string{}}, + ) + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "1", Dependencies: []string{"6", "3"}}, + flag{Key: "2", Dependencies: []string{"8", "5", "3", "1"}}, + flag{Key: "9", Dependencies: []string{"10", "7", "5"}}, + flag{Key: "20", Dependencies: []string{}}, + flag{Key: "21", Dependencies: []string{"20"}}, + flag{Key: "30", Dependencies: []string{}}, + ) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } +} +func TestComplexNoCycleStartingWithRoot(t *testing.T) { + inputFlags := flagsArray( + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "1", Dependencies: []string{"6", "3"}}, + flag{Key: "2", Dependencies: []string{"8", "5", "3", "1"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "9", Dependencies: []string{"10", "7", "5"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "20", Dependencies: []string{}}, + flag{Key: "21", Dependencies: []string{"20"}}, + flag{Key: "30", Dependencies: []string{}}, + ) + inputFlagKeys := make([]string, 0) + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "1", Dependencies: []string{"6", "3"}}, + flag{Key: "2", Dependencies: []string{"8", "5", "3", "1"}}, + flag{Key: "9", Dependencies: []string{"10", "7", "5"}}, + flag{Key: "20", Dependencies: []string{}}, + flag{Key: "21", Dependencies: []string{"20"}}, + flag{Key: "30", Dependencies: []string{}}, + ) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } +} + +// Utilities + +type flag struct { + Key string + Dependencies []string +} + +func flagsArray(flags ...flag) []interface{} { + result := make([]interface{}, 0) + for _, f := range flags { + result = append(result, flagInterface(f)) + } + return result +} + +func flagInterface(flag flag) interface{} { + dependencyMap := make(map[string]interface{}) + for _, dependency := range flag.Dependencies { + dependencyMap[dependency] = true + } + return map[string]interface{}{ + "flagKey": flag.Key, + "parentDependencies": map[string]interface{}{ + "flags": dependencyMap, + }, + } +} + +// Used for testing to ensure the correct ordering of iteration. +func topologicalSortArray(flags []interface{}, flagKeys []string) ([]interface{}, error) { + // Extract keys and create flags map + keys := make([]string, 0) + available := make(map[string]interface{}) + for _, flagAny := range flags { + switch flag := flagAny.(type) { + case map[string]interface{}: + switch flagKey := flag["flagKey"].(type) { + case string: + keys = append(keys, flagKey) + available[flagKey] = flag + } + } + } + // Get the starting keys + startingKeys := make([]string, 0) + if len(flagKeys) > 0 { + startingKeys = flagKeys + } else { + startingKeys = keys + } + return topologicalSort(available, startingKeys) +} From e86ca0dde06b5c5de0a8190d11c35e546d7c0b5f Mon Sep 17 00:00:00 2001 From: Brian Giori Date: Thu, 3 Aug 2023 11:32:46 -0700 Subject: [PATCH 2/3] fix: lint --- pkg/experiment/local/sort.go | 5 ++--- pkg/experiment/local/sort_test.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/experiment/local/sort.go b/pkg/experiment/local/sort.go index a8f9dc8..a45bd53 100644 --- a/pkg/experiment/local/sort.go +++ b/pkg/experiment/local/sort.go @@ -12,7 +12,7 @@ func topologicalSort(flags map[string]interface{}, flagKeys []string) ([]interfa available[k] = v } // Get the starting keys - startingKeys := make([]string, 0) + var startingKeys []string if len(flagKeys) > 0 { startingKeys = flagKeys } else { @@ -56,7 +56,6 @@ func parentTraversal(flagKey string, available map[string]interface{}, path []st } } result = append(result, flag) - path = path[:len(path)-1] delete(available, flagKey) return result, nil } @@ -77,7 +76,7 @@ func extractDependencies(flag interface{}) []string { switch flags := flagsAny.(type) { case map[string]interface{}: result := make([]string, 0) - for k, _ := range flags { + for k := range flags { result = append(result, k) } return result diff --git a/pkg/experiment/local/sort_test.go b/pkg/experiment/local/sort_test.go index 4f4d6da..04129c9 100644 --- a/pkg/experiment/local/sort_test.go +++ b/pkg/experiment/local/sort_test.go @@ -425,7 +425,7 @@ func topologicalSortArray(flags []interface{}, flagKeys []string) ([]interface{} } } // Get the starting keys - startingKeys := make([]string, 0) + var startingKeys []string if len(flagKeys) > 0 { startingKeys = flagKeys } else { From 54999bf7c7b3e046157bb5be4053b60ef23fa89c Mon Sep 17 00:00:00 2001 From: Brian Giori Date: Thu, 3 Aug 2023 11:54:42 -0700 Subject: [PATCH 3/3] add test case --- pkg/experiment/local/sort_test.go | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pkg/experiment/local/sort_test.go b/pkg/experiment/local/sort_test.go index 04129c9..c45b370 100644 --- a/pkg/experiment/local/sort_test.go +++ b/pkg/experiment/local/sort_test.go @@ -381,6 +381,39 @@ func TestComplexNoCycleStartingWithRoot(t *testing.T) { } } +func TestComplexNoCycleWithFlagKeys(t *testing.T) { + inputFlags := flagsArray( + flag{Key: "1", Dependencies: []string{"6", "3"}}, + flag{Key: "2", Dependencies: []string{"8", "5", "3", "1"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "9", Dependencies: []string{"10", "7", "5"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "20", Dependencies: []string{}}, + flag{Key: "21", Dependencies: []string{"20"}}, + flag{Key: "30", Dependencies: []string{}}, + ) + inputFlagKeys := []string{"1"} + actual, _ := topologicalSortArray(inputFlags, inputFlagKeys) + expected := flagsArray( + flag{Key: "8", Dependencies: []string{}}, + flag{Key: "7", Dependencies: []string{"8"}}, + flag{Key: "4", Dependencies: []string{"8", "7"}}, + flag{Key: "6", Dependencies: []string{"7", "4"}}, + flag{Key: "10", Dependencies: []string{"7"}}, + flag{Key: "5", Dependencies: []string{"10", "7"}}, + flag{Key: "3", Dependencies: []string{"6", "5"}}, + flag{Key: "1", Dependencies: []string{"6", "3"}}, + ) + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected %v, actual %v", expected, actual) + } +} + // Utilities type flag struct {