Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix hash function to work on different workers the same #9

Merged
merged 1 commit into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion featureflags_client/http/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Any, Callable, Dict, List, Optional, Set

from featureflags_client.http.types import Check, Flag, Operator
from featureflags_client.http.utils import hash_flag_value

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -79,7 +80,11 @@ def percent(name: str, value: Any) -> Callable:
@except_false
def proc(ctx: Dict[str, Any]) -> bool:
ctx_val = ctx.get(name, _UNDEFINED)
return ctx_val is not _UNDEFINED and hash(ctx_val) % 100 < int(value)
if ctx_val is _UNDEFINED:
return False

hash_ctx_val = hash_flag_value(name, ctx_val)
return hash_ctx_val % 100 < int(value)

return proc

Expand Down
8 changes: 8 additions & 0 deletions featureflags_client/http/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import hashlib
import inspect
import struct
from enum import Enum, EnumMeta
from typing import Any, Dict, Generator, Mapping, Type, Union

Expand Down Expand Up @@ -54,3 +56,9 @@ def intervals_gen(
else:
success = yield retry_interval
retry_interval = min(retry_interval * 2, retry_interval_max)


def hash_flag_value(name: str, value: Any) -> int:
hash_digest = hashlib.md5(f"{name}{value}".encode()).digest() # noqa: S324
(hash_int,) = struct.unpack("<L", hash_digest[-4:])
return hash_int
33 changes: 22 additions & 11 deletions featureflags_client/tests/http/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@
wildcard,
)
from featureflags_client.http.types import Operator
from featureflags_client.http.utils import hash_flag_value

TEST_OPERATOR_NAME = "test_operator"
TEST_VARIABLE_NAME = "test_operator"


def check_op(left: Any, op: Callable, right: Any) -> bool:
return op("var", right)({"var": left} if left is not _UNDEFINED else {})
context = {TEST_VARIABLE_NAME: left} if left is not _UNDEFINED else {}
return op(TEST_OPERATOR_NAME, right)(context)


def test_false():
Expand Down Expand Up @@ -82,24 +87,30 @@ def test_contains():


def test_percent():
assert check_op(0, percent, 1) is True
assert check_op(1, percent, 1) is False
assert check_op(1, percent, 2) is True

# If percent <= 0 return False
for i in range(-150, 150):
assert check_op(i, percent, 0) is False

# If percent >= 100 return True
for i in range(-150, 150):
assert check_op(i, percent, 100) is True

assert check_op("foo", percent, 100) is True
assert check_op("foo", percent, 0) is False
assert check_op("foo", percent, hash("foo") % 100 + 1) is True
assert check_op("foo", percent, hash("foo") % 100 - 1) is False

# Check not integer values
assert check_op(_UNDEFINED, percent, 100) is False
assert check_op(50, percent, _UNDEFINED) is False
assert check_op(_UNDEFINED, percent, _UNDEFINED) is False
assert check_op("foo", percent, "not_number") is False

# Check string values
assert check_op("foo", percent, "100") is True
assert check_op("foo", percent, "not_number") is False
assert check_op("foo", percent, "0") is False
assert check_op("foo", percent, 100) is True
assert check_op("foo", percent, 0) is False

# Check hash comparison
foo_hash = hash_flag_value(TEST_VARIABLE_NAME, "foo")
assert check_op("foo", percent, foo_hash % 100 + 1) is True
assert check_op("foo", percent, foo_hash % 100 - 1) is False


def test_regexp():
Expand Down
Loading