Skip to content

Commit 864085a

Browse files
authored
Fix evaluation of numeric attributes that are not passed in as strings or float64s (#53)
* failing test * tests passing * bump version * tab instead of space * feedback from PR * buffer time for test * expect 6 poller calls with explanation why
1 parent ae2e6de commit 864085a

File tree

6 files changed

+112
-46
lines changed

6 files changed

+112
-46
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test-data:
2222
git clone -b ${branchName} --depth 1 --single-branch ${githubRepoLink} ${testDataDir}
2323

2424
test: test-data
25-
go test ./...
25+
go test -v ./...
2626

2727
lint:
2828
golangci-lint run

eppoclient/initclient.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package eppoclient
44

55
import "net/http"
66

7-
var __version__ = "4.0.0"
7+
var __version__ = "4.0.1"
88

99
// InitClient is required to start polling of experiments configurations and create
1010
// an instance of EppoClient, which could be used to get assignments information.

eppoclient/poller_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ func (m *CallbackMock) CallbackFn() {
1717
}
1818

1919
func Test_PollerPoll_InvokesCallbackUntilStoped(t *testing.T) {
20-
expected := 5
21-
2220
callbackMock := CallbackMock{}
2321
callbackMock.On("CallbackFn").Return()
2422

2523
var poller = newPoller(1*time.Second, callbackMock.CallbackFn)
2624
poller.Start()
27-
time.Sleep(5 * time.Second)
25+
time.Sleep(5 * time.Second + 500 * time.Millisecond) // half second buffer to allow polling thread to execute
2826
poller.Stop()
29-
27+
expected := 6 // One call for start(), and then another call each second for 5 seconds before stopped at 5.5 seconds
3028
callbackMock.AssertNumberOfCalls(t, "CallbackFn", expected)
3129
}
3230

eppoclient/rules.go

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -159,38 +159,6 @@ func isOne(attributeValue interface{}, s string) bool {
159159
}
160160
}
161161

162-
func promoteInt(i interface{}) int64 {
163-
switch i := i.(type) {
164-
case int:
165-
return int64(i)
166-
case int8:
167-
return int64(i)
168-
case int16:
169-
return int64(i)
170-
case int32:
171-
return int64(i)
172-
case int64:
173-
return i
174-
}
175-
panic(fmt.Errorf("unexpected type passed to promoteInt: %T", i))
176-
}
177-
178-
func promoteUint(i interface{}) uint64 {
179-
switch i := i.(type) {
180-
case uint:
181-
return uint64(i)
182-
case uint8:
183-
return uint64(i)
184-
case uint16:
185-
return uint64(i)
186-
case uint32:
187-
return uint64(i)
188-
case uint64:
189-
return i
190-
}
191-
panic(fmt.Errorf("unexpected type passed to promoteUint: %T", i))
192-
}
193-
194162
func evaluateSemVerCondition(subjectValue *semver.Version, conditionValue *semver.Version, condition condition) bool {
195163
comp := subjectValue.Compare(conditionValue)
196164
switch condition.Operator {
@@ -227,15 +195,61 @@ func evaluateNumericCondition(subjectValue float64, conditionValue float64, cond
227195
// Returns a float64 and nil error on success, or 0 and an error on failure.
228196
func toFloat64(val interface{}) (float64, error) {
229197
switch v := val.(type) {
230-
case float64:
231-
return v, nil
198+
case float32, float64:
199+
return promoteFloat(v), nil
200+
case int, int8, int16, int32, int64:
201+
return float64(promoteInt(v)), nil
202+
case uint, uint8, uint16, uint32, uint64:
203+
return float64(promoteUint(v)), nil
232204
case string:
233205
floatVal, err := strconv.ParseFloat(v, 64)
234206
if err != nil {
235207
return 0, fmt.Errorf("cannot convert string '%s' to float64: %w", v, err)
236208
}
237209
return floatVal, nil
238210
default:
239-
return 0, errors.New("value is neither a float64 nor a convertible string")
211+
return 0, errors.New("value is neither a number nor a convertible string")
212+
}
213+
}
214+
215+
func promoteInt(i interface{}) int64 {
216+
switch i := i.(type) {
217+
case int:
218+
return int64(i)
219+
case int8:
220+
return int64(i)
221+
case int16:
222+
return int64(i)
223+
case int32:
224+
return int64(i)
225+
case int64:
226+
return i
227+
}
228+
panic(fmt.Errorf("unexpected type passed to promoteInt: %T", i))
229+
}
230+
231+
func promoteUint(i interface{}) uint64 {
232+
switch i := i.(type) {
233+
case uint:
234+
return uint64(i)
235+
case uint8:
236+
return uint64(i)
237+
case uint16:
238+
return uint64(i)
239+
case uint32:
240+
return uint64(i)
241+
case uint64:
242+
return i
243+
}
244+
panic(fmt.Errorf("unexpected type passed to promoteUint: %T", i))
245+
}
246+
247+
func promoteFloat(f interface{}) float64 {
248+
switch f := f.(type) {
249+
case float32:
250+
return float64(f)
251+
case float64:
252+
return f
240253
}
254+
panic(fmt.Errorf("unexpected type passed to promoteFloat: %T", f))
241255
}

eppoclient/rules_test.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,14 @@ func Test_isNotOneOf_Fail(t *testing.T) {
225225
assert.Equal(t, expected, result)
226226
}
227227

228-
func Test_evaluateNumericcondition_Success(t *testing.T) {
228+
func Test_evaluateNumericcondition_Fail(t *testing.T) {
229229
expected := false
230230
result := evaluateNumericCondition(40, 30.0, condition{Operator: "LT", Value: 30.0})
231231

232232
assert.Equal(t, expected, result)
233233
}
234234

235-
func Test_evaluateNumericcondition_Fail(t *testing.T) {
235+
func Test_evaluateNumericcondition_Success(t *testing.T) {
236236
expected := true
237237
result := evaluateNumericCondition(25, 30.0, condition{Operator: "LT", Value: 30.0})
238238

@@ -281,3 +281,45 @@ func Test_isNotNull_attributePresent(t *testing.T) {
281281
})
282282
assert.True(t, result)
283283
}
284+
285+
func Test_handles_all_numeric_types(t *testing.T) {
286+
condition := condition{Operator: "GT", Attribute: "powerLevel", Value: "9000"}
287+
// Floats
288+
assert.True(t, condition.matches(Attributes{ "powerLevel": 9001.0}) )
289+
assert.False(t, condition.matches(Attributes{ "powerLevel": 9000.0}) )
290+
assert.True(t, condition.matches(Attributes{ "powerLevel": float64(9001)}) )
291+
assert.False(t, condition.matches(Attributes{ "powerLevel": float64(-9001.0)}) )
292+
assert.True(t, condition.matches(Attributes{ "powerLevel": float32(9001)}) )
293+
assert.False(t, condition.matches(Attributes{ "powerLevel": float32(8999)}) )
294+
// Signed Integers
295+
assert.True(t, condition.matches(Attributes{ "powerLevel": 9001}) )
296+
assert.False(t, condition.matches(Attributes{ "powerLevel": 9000}) )
297+
assert.False(t, condition.matches(Attributes{ "powerLevel": int8(1)}) )
298+
assert.True(t, condition.matches(Attributes{ "powerLevel": int16(9001)}) )
299+
assert.False(t, condition.matches(Attributes{ "powerLevel": int16(-9002)}) )
300+
assert.True(t, condition.matches(Attributes{ "powerLevel": int32(10000)}) )
301+
assert.False(t, condition.matches(Attributes{ "powerLevel": int32(0)}) )
302+
assert.True(t, condition.matches(Attributes{ "powerLevel": int64(9001)}) )
303+
assert.False(t, condition.matches(Attributes{ "powerLevel": int64(8999)}) )
304+
// Unsigned Integers
305+
assert.False(t, condition.matches(Attributes{ "powerLevel": uint8(1)}) )
306+
assert.True(t, condition.matches(Attributes{ "powerLevel": uint16(9001)}) )
307+
assert.False(t, condition.matches(Attributes{ "powerLevel": uint16(8999)}) )
308+
assert.True(t, condition.matches(Attributes{ "powerLevel": uint32(10000)}) )
309+
assert.False(t, condition.matches(Attributes{ "powerLevel": uint32(0)}) )
310+
assert.True(t, condition.matches(Attributes{ "powerLevel": uint64(9001)}) )
311+
assert.False(t, condition.matches(Attributes{ "powerLevel": uint64(8999)}) )
312+
// Strings
313+
assert.True(t, condition.matches(Attributes{ "powerLevel": "9001"}) )
314+
assert.True(t, condition.matches(Attributes{ "powerLevel": "9000.1"}) )
315+
assert.False(t, condition.matches(Attributes{ "powerLevel": "9000"}) )
316+
assert.False(t, condition.matches(Attributes{ "powerLevel": ".2"}) )
317+
}
318+
319+
func Test_invalid_numeric_types(t *testing.T) {
320+
condition := condition{Operator: "GT", Attribute: "powerLevel", Value: "9000"}
321+
assert.False(t, condition.matches(Attributes{ "powerLevel": "empty"}) )
322+
assert.False(t, condition.matches(Attributes{ "powerLevel": ""}) )
323+
assert.False(t, condition.matches(Attributes{ "powerLevel": false}) )
324+
assert.False(t, condition.matches(Attributes{ "powerLevel": true}) )
325+
}

eppoclient/utils_test.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package eppoclient
22

33
import (
4+
"math"
45
"testing"
56
)
67

@@ -12,11 +13,21 @@ func TestToFloat64(t *testing.T) {
1213
expectErr bool
1314
}{
1415
{"Float64Input", 123.456, 123.456, false},
15-
{"StringInputValid", "789.012", 789.012, false},
16+
{"Float32Input", float32(123.456), 123.456, false},
17+
{"Int8Input", int8(123), 123.0, false},
18+
{"Int16Input", int16(123), 123.0, false},
19+
{"Int32Input", int16(123), 123.0, false},
20+
{"Int64Input", int16(123), 123.0, false},
21+
{"UInt8Input", uint8(123), 123.0, false},
22+
{"UInt16Input", uint16(123), 123.0, false},
23+
{"UInt32Input", uint16(123), 123.0, false},
24+
{"UInt64Input", uint16(123), 123.0, false},
25+
{"StringIntInputValid", "789", 789.0, false},
26+
{"StringFloatInputValid", "789.012", 789.012, false},
27+
{"StringNegativeInputValid", "-789.012", -789.012, false},
1628
{"StringInputInvalid", "abc", 0, true},
1729
{"SemVerInputInvalid", "1.2.3", 0, true},
1830
{"BoolInput", true, 0, true},
19-
{"IntInput", 123, 0, true},
2031
}
2132

2233
for _, tt := range tests {
@@ -26,7 +37,8 @@ func TestToFloat64(t *testing.T) {
2637
t.Errorf("ToFloat64(%v) error = %v, expectErr %v", tt.input, err, tt.expectErr)
2738
return
2839
}
29-
if !tt.expectErr && result != tt.expected {
40+
closeEnough := math.Abs(result - tt.expected)/tt.expected < 0.00001
41+
if !tt.expectErr && !closeEnough {
3042
t.Errorf("ToFloat64(%v) = %v, want %v", tt.input, result, tt.expected)
3143
}
3244
})

0 commit comments

Comments
 (0)