Skip to content

Commit

Permalink
Mutex lock and testcases added
Browse files Browse the repository at this point in the history
  • Loading branch information
ozayr-zaviar committed Nov 3, 2021
1 parent 68146a1 commit a261899
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 12 deletions.
30 changes: 18 additions & 12 deletions optimizely/optimizely_user_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def __init__(self, optimizely_client, user_id, user_attributes=None):

self._user_attributes = user_attributes.copy() if user_attributes else {}
self.lock = threading.Lock()
self.forced_decisions = {}
with self.lock:
self.forced_decisions = {}
self.log = logger.SimpleLogger(min_level=enums.LogLevels.INFO)

# decision context
Expand All @@ -72,8 +73,9 @@ def _clone(self):

user_context = OptimizelyUserContext(self.client, self.user_id, self.get_user_attributes())

if self.forced_decisions:
user_context.forced_decisions = copy.deepcopy(self.forced_decisions)
with self.lock:
if self.forced_decisions:
user_context.forced_decisions = copy.deepcopy(self.forced_decisions)

return user_context

Expand Down Expand Up @@ -167,7 +169,8 @@ def set_forced_decision(self, OptimizelyDecisionContext, OptimizelyForcedDecisio
context = OptimizelyDecisionContext
decision = OptimizelyForcedDecision

self.forced_decisions[context] = decision
with self.lock:
self.forced_decisions[context] = decision

return True

Expand Down Expand Up @@ -207,9 +210,10 @@ def remove_forced_decision(self, OptimizelyDecisionContext):
self.log.logger.error(OptimizelyDecisionMessage.SDK_NOT_READY)
return False

if self.forced_decisions[OptimizelyDecisionContext]:
del self.forced_decisions[OptimizelyDecisionContext]
return True
with self.lock:
if self.forced_decisions[OptimizelyDecisionContext]:
del self.forced_decisions[OptimizelyDecisionContext]
return True

return False

Expand All @@ -226,17 +230,19 @@ def remove_all_forced_decisions(self):
self.log.logger.error(OptimizelyDecisionMessage.SDK_NOT_READY)
return False

self.forced_decisions.clear()
with self.lock:
self.forced_decisions.clear()

return True

def find_forced_decision(self, OptimizelyDecisionContext):

if not self.forced_decisions:
return None
with self.lock:
if not self.forced_decisions:
return None

# must allow None to be returned for the Flags only case
return self.forced_decisions.get(OptimizelyDecisionContext)
# must allow None to be returned for the Flags only case
return self.forced_decisions.get(OptimizelyDecisionContext)

def find_validated_forced_decision(self, OptimizelyDecisionContext, options):

Expand Down
111 changes: 111 additions & 0 deletions tests/test_user_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import json

import mock
import threading

from optimizely import optimizely, decision_service
from optimizely.decision.optimizely_decide_option import OptimizelyDecideOption as DecideOption
Expand Down Expand Up @@ -1793,3 +1794,113 @@ def test_forced_decision_return_status(self):
self.assertTrue(status)
status = user_context.remove_all_forced_decisions()
self.assertTrue(status)

def test_forced_decision_clone_return_valid_forced_decision(self):
"""
Should return valid forced decision on cloning.
"""
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features))
user_context = OptimizelyUserContext(opt_obj, "test_user", {})

context_with_flag = OptimizelyUserContext.OptimizelyDecisionContext('f1', None)
decision_for_flag = OptimizelyUserContext.OptimizelyForcedDecision('v1')
context_with_rule = OptimizelyUserContext.OptimizelyDecisionContext('f1', 'r1')
decision_for_rule = OptimizelyUserContext.OptimizelyForcedDecision('v2')
context_with_empty_rule = OptimizelyUserContext.OptimizelyDecisionContext('f1', '')
decision_for_empty_rule = OptimizelyUserContext.OptimizelyForcedDecision('v3')

user_context.set_forced_decision(context_with_flag, decision_for_flag)
user_context.set_forced_decision(context_with_rule, decision_for_rule)
user_context.set_forced_decision(context_with_empty_rule, decision_for_empty_rule)

user_context_2 = user_context._clone()
self.assertEqual(user_context_2.user_id, 'test_user')
self.assertEqual(user_context_2.get_user_attributes(), {})
self.assertIsNotNone(user_context_2.forced_decisions)

self.assertEqual(user_context_2.get_forced_decision(context_with_flag).variation_key, 'v1')
self.assertEqual(user_context_2.get_forced_decision(context_with_rule).variation_key, 'v2')
self.assertEqual(user_context_2.get_forced_decision(context_with_empty_rule).variation_key, 'v3')

context_with_rule = OptimizelyUserContext.OptimizelyDecisionContext('x', 'y')
decision_for_rule = OptimizelyUserContext.OptimizelyForcedDecision('z')
user_context.set_forced_decision(context_with_rule, decision_for_rule)
self.assertEqual(user_context.get_forced_decision(context_with_rule).variation_key, 'z')
self.assertIsNone(user_context_2.get_forced_decision(context_with_rule))

def test_forced_decision_sync_return_correct_number_of_calls(self):
"""
Should return valid number of call on running forced decision calls in thread.
"""
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features))
user_context = OptimizelyUserContext(opt_obj, "test_user", {})
context_1 = OptimizelyUserContext.OptimizelyDecisionContext('f1', None)
decision_1 = OptimizelyUserContext.OptimizelyForcedDecision('v1')
context_2 = OptimizelyUserContext.OptimizelyDecisionContext('f2', None)
decision_2 = OptimizelyUserContext.OptimizelyForcedDecision('v1')

with mock.patch(
'optimizely.optimizely_user_context.OptimizelyUserContext.set_forced_decision'
) as set_forced_decision_mock, mock.patch(
'optimizely.optimizely_user_context.OptimizelyUserContext.get_forced_decision'
) as get_forced_decision_mock, mock.patch(
'optimizely.optimizely_user_context.OptimizelyUserContext.remove_forced_decision'
) as remove_forced_decision_mock, mock.patch(
'optimizely.optimizely_user_context.OptimizelyUserContext.remove_all_forced_decisions'
) as remove_all_forced_decisions_mock, mock.patch(
'optimizely.optimizely_user_context.OptimizelyUserContext._clone'
) as clone_mock:
def set_forced_decision_loop(user_context, context, decision):
for x in range(100):
user_context.set_forced_decision(context, decision)

def get_forced_decision_loop(user_context, context):
for x in range(100):
user_context.get_forced_decision(context)

def remove_forced_decision_loop(user_context, context):
for x in range(100):
user_context.remove_forced_decision(context)

def remove_all_forced_decisions_loop(user_context):
for x in range(100):
user_context.remove_all_forced_decisions()

def clone_loop(user_context):
for x in range(100):
user_context._clone()

set_thread_1 = threading.Thread(target=set_forced_decision_loop, args=(user_context, context_1, decision_1))
set_thread_2 = threading.Thread(target=set_forced_decision_loop, args=(user_context, context_2, decision_2))
set_thread_3 = threading.Thread(target=get_forced_decision_loop, args=(user_context, context_1))
set_thread_4 = threading.Thread(target=get_forced_decision_loop, args=(user_context, context_2))
set_thread_5 = threading.Thread(target=remove_forced_decision_loop, args=(user_context, context_1))
set_thread_6 = threading.Thread(target=remove_forced_decision_loop, args=(user_context, context_2))
set_thread_7 = threading.Thread(target=remove_all_forced_decisions_loop, args=(user_context,))
set_thread_8 = threading.Thread(target=clone_loop, args=(user_context,))

# Starting the threads
set_thread_1.start()
set_thread_2.start()
set_thread_3.start()
set_thread_4.start()
set_thread_5.start()
set_thread_6.start()
set_thread_7.start()
set_thread_8.start()

# Waiting for all the threads to finish executing
set_thread_1.join()
set_thread_2.join()
set_thread_3.join()
set_thread_4.join()
set_thread_5.join()
set_thread_6.join()
set_thread_7.join()
set_thread_8.join()

self.assertEqual(200, set_forced_decision_mock.call_count)
self.assertEqual(200, get_forced_decision_mock.call_count)
self.assertEqual(200, remove_forced_decision_mock.call_count)
self.assertEqual(100, remove_all_forced_decisions_mock.call_count)
self.assertEqual(100, clone_mock.call_count)

0 comments on commit a261899

Please sign in to comment.