Skip to content

Commit

Permalink
[FFM-5387]: Fix Golang Group Rule Evaluate and Add Tests (#101)
Browse files Browse the repository at this point in the history
Run evaluator tests against the ff-test-grid test-cases
Improve test coverage
Fix the group rule evaluator.  Currently rules are being AND'd but they
should be OR'd
  • Loading branch information
davejohnston committed Nov 25, 2022
1 parent 875e3de commit 8dcc0c3
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 24 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ test:
go test -race -v --cover ./...

report:
go test ./... -covermode=atomic -coverpkg=./... -coverprofile=c.out
gocov convert ./c.out | gocov-html > ~/go-sdk-test-report.html
go test -race -v -covermode=atomic -coverprofile=cover.out ./... | tee /dev/stderr | go-junit-report -set-exit-code > junit.xml
gocov convert ./cover.out | gocov-html > coverage-report.html


build-test-wrapper:
Expand Down
23 changes: 19 additions & 4 deletions evaluation/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ func (e Evaluator) evaluateRules(servingRules []rest.ServingRule, target *Target
return ""
}

// evaluateGroupRules evaluates the groups rules. Note Group rule are represented by a rest.Clause, instead
// of a rest.Rule. Unlike feature clauses which are AND'd, in a case of a group these must be OR'd.
func (e Evaluator) evaluateGroupRules(rules []rest.Clause, target *Target) (bool, rest.Clause) {
for _, r := range rules {
rule := r
if e.evaluateClause(&rule, target) {
return true, r
}
}
return false, rest.Clause{}
}

func (e Evaluator) evaluateVariationMap(variationsMap []rest.VariationMap, target *Target) string {
if variationsMap == nil || target == nil {
return ""
Expand Down Expand Up @@ -252,11 +264,14 @@ func (e Evaluator) isTargetIncludedOrExcludedInSegment(segmentList []string, tar
// Should Target be included via segment rules
rules := segment.Rules
// if rules is nil pointer or points to the empty slice
if (rules != nil && len(*rules) > 0) && e.evaluateClauses(*rules, target) {
e.logger.Debugf(
"Target %s included in segment %s via rules", target.Name, segment.Name)
return true
if rules != nil && len(*rules) > 0 {
if included, clause := e.evaluateGroupRules(*rules, target); included {
e.logger.Debugf(
"Target [%s] included in group [%s] via rule %+v", target.Name, segment.Name, clause)
return true
}
}

}
return false
}
Expand Down
53 changes: 36 additions & 17 deletions tests/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"

"github.com/stretchr/testify/assert"

"github.com/harness/ff-golang-server-sdk/logger"

"github.com/harness/ff-golang-server-sdk/evaluation"
Expand All @@ -34,25 +37,32 @@ type testFile struct {
}

func loadFiles() []testFile {
files, err := ioutil.ReadDir(source)

slice := []testFile{}
err := filepath.Walk(source,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || filepath.Ext(info.Name()) != ".json" {
return nil
}
if f, err := loadFile(path); err == nil {
slice = append(slice, f)
} else {
log.Errorf("unable to load %s because %v", info.Name(), err)
}
return nil
})
if err != nil {
log.Error(err)
}

slice := make([]testFile, 0, len(files))
for _, file := range files {
if file.IsDir() || filepath.Ext(file.Name()) != ".json" {
continue
}
if f, err := loadFile(file.Name()); err == nil {
slice = append(slice, f)
}
}
return slice
}

func loadFile(filename string) (testFile, error) {
fp := filepath.Clean(filepath.Join(source, filename))
fp := filepath.Clean(filename)
content, err := ioutil.ReadFile(fp)
if err != nil {
log.Error(err)
Expand Down Expand Up @@ -91,7 +101,7 @@ func TestEvaluator(t *testing.T) {
}

for _, testCase := range fixture.Tests {
testName := fmt.Sprintf("test fixture %s with flag %s", fixture.Filename, testCase.Flag)
testName := fixture.Filename
if testCase.Target != nil {
testName = fmt.Sprintf("%s and target %s", testName, *testCase.Target)
}
Expand All @@ -114,16 +124,25 @@ func TestEvaluator(t *testing.T) {
got = evaluator.BoolVariation(testCase.Flag, target, false)
case rest.FeatureConfigKindString:
got = evaluator.StringVariation(testCase.Flag, target, "blue")
case rest.FeatureConfigKindInt:
got = evaluator.IntVariation(testCase.Flag, target, 100)
case "number":
case rest.FeatureConfigKindInt, "number":
got = evaluator.NumberVariation(testCase.Flag, target, 50.00)
case rest.FeatureConfigKindJson:
got = evaluator.JSONVariation(testCase.Flag, target, map[string]interface{}{})
str, _ := json.Marshal(&got)
got = string(str)

}
if !reflect.DeepEqual(got, testCase.Expected) {
t.Errorf("eval engine got = %v, want %v", got, testCase.Expected)

if flag.Kind == rest.FeatureConfigKindJson {
expected := fmt.Sprintf("%s", testCase.Expected)
jsonStr := fmt.Sprintf("%s", got)
assert.JSONEq(t, expected, jsonStr, "eval engine got = [%v], want [%v]", jsonStr, expected)
} else {
if !reflect.DeepEqual(got, testCase.Expected) {
t.Errorf("eval engine got = [%v], want [%v]", got, testCase.Expected)
}
}

})
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ff-test-cases
Submodule ff-test-cases updated 79 files
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_False_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_False_Off_True_Should_Return_True.json
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_True_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_True_Off_True_Should_Return_True.json
+621 −0 ...OffRules/DefaultOffRules_Type_JSON_State_Disabled_On_complexJSON_Off_emptyJSON_Should_Return_validJSON.json
+621 −0 ...ltOffRules/DefaultOffRules_Type_JSON_State_Disabled_On_validJSON_Off_emptyJSON_Should_Return_emptyJSON.json
+621 −0 tests/DefaultOffRules/DefaultOffRules_Type_Number_State_Disabled_On_011_Off_2_Should_Return_2.json
+621 −0 tests/DefaultOffRules/DefaultOffRules_Type_Number_State_Disabled_On_324523_Off_011_Should_Return_011.json
+621 −0 ...ultOffRules/DefaultOffRules_Type_String_State_Disabled_On_sonnet19_Off_sonnet53_Should_Return_sonnet53.json
+621 −0 ...ultOffRules/DefaultOffRules_Type_String_State_Disabled_On_sonnet53_Off_sonnet19_Should_Return_sonnet19.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_False_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_False_Off_True_Should_Return_True.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_True_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_True_Off_True_Should_Return_True.json
+621 −0 ...tOnRules/DefaultOnRules_Type_JSON_State_Enabled_On_complexJSON_Off_emptyJSON_Should_Return_complexJSON.json
+621 −0 ...faultOnRules/DefaultOnRules_Type_JSON_State_Enabled_On_validJSON_Off_emptyJSON_Should_Return_validJSON.json
+621 −0 tests/DefaultOnRules/DefaultOnRules_Type_Number_State_Enabled_On_011_Off_2_Should_Return_011.json
+621 −0 tests/DefaultOnRules/DefaultOnRules_Type_Number_State_Enabled_On_324523_Off_011_Should_Return_324523.json
+621 −0 ...efaultOnRules/DefaultOnRules_Type_String_State_Enabled_On_sonnet19_Off_sonnet53_Should_Return_sonnet19.json
+621 −0 ...efaultOnRules/DefaultOnRules_Type_String_State_Enabled_On_sonnet53_Off_sonnet19_Should_Return_sonnet53.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_EqualsThree_Should_Return_false_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_EqualsThree_Should_Return_true_for_Target_one.json
+634 −0 ...pRules/GroupRules_Type_Bool_State_Enabled_GroupWithMultipleOrRules_Should_Return_true_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_InOneTwoThree_Should_Return_true_for_Target_three.json
+634 −0 ...oupRules/GroupRules_Type_Bool_State_Enabled_IncludeAndExcludeGroup_Should_Return_true_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_IncludedOnly_Should_Return_true_for_Target_three.json
+634 −0 ...pRules/GroupRules_Type_Bool_State_Enabled_StartsWithTExcludesThree_Should_Return_true_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_StartsWithT_Should_Return_false_for_Target_one.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_StartsWithT_Should_Return_true_for_Target_three.json
+622 −0 ...rerequisites/Type_BoolBool_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_False.json
+622 −0 ...Prerequisites/Type_BoolBool_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_True.json
+622 −0 .../Prerequisites/Type_BoolBool_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_False.json
+622 −0 ...s/Prerequisites/Type_BoolBool_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_True.json
+622 −0 ...Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_False.json
+622 −0 .../Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_True.json
+622 −0 ...s/Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_False.json
+622 −0 tests/Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_True.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOff_Should_Return_Com.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOff_Should_Return_Val.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOn_Should_Return_Com.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOn_Should_Return_Val.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOff_Should_Return_off.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOff_Should_Return_on.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOn_Should_Return_Off.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOn_Should_Return_On.json
+638 −0 tests/TargetRules/TargetRules_Type_Bool_State_Disabled_Should_Return_Default_Off_Value.json
+638 −0 tests/TargetRules/TargetRules_Type_Bool_State_Enabled_Should_Return_false_for_Target_four.json
+659 −0 tests/TargetRules/TargetRules_Type_Bool_State_Enabled_Should_Return_false_for_Target_six.json
+638 −0 tests/TargetRules/TargetRules_Type_Bool_State_Enabled_Should_Return_true_for_Target_three.json
+665 −0 tests/TargetRules/TargetRules_Type_JSON_State_Enabled_Should_Return_complexJSON_for_Target_five.json
+665 −0 tests/TargetRules/TargetRules_Type_JSON_State_Enabled_Should_Return_emptyJSON_for_Target_four.json
+665 −0 tests/TargetRules/TargetRules_Type_JSON_State_Enabled_Should_Return_validJSON_for_Target_anonymous.json
+665 −0 tests/TargetRules/TargetRules_Type_Number_State_Enabled_Should_Return_2_for_Target_four.json
+665 −0 tests/TargetRules/TargetRules_Type_Number_State_Enabled_Should_Return_324523_for_Target_five.json
+665 −0 tests/TargetRules/TargetRules_Type_Number_State_Enabled_Should_Return_sonnet53_for_Target_anonymous.json
+665 −0 tests/TargetRules/TargetRules_Type_String_State_Enabled_Should_Return_sonnet53_for_Target_anonymous.json
+665 −0 tests/TargetRules/TargetRules_Type_String_State_Enabled_Should_Return_sonnet53_for_Target_four.json
+665 −0 tests/TargetRules/TargetRules_Type_String_State_Enabled_Should_Return_thehobbit_for_Target_five.json
+4 −4 tests/bool_on_simple_rule.json
+3 −3 tests/off_flag.json
+3 −3 tests/off_off_no_rules.json
+3 −3 tests/on_off_no_rules.json
+6 −6 tests/prereq.json

0 comments on commit 8dcc0c3

Please sign in to comment.