Skip to content

Commit

Permalink
fix: Move tests under ldclient namespace (#29)
Browse files Browse the repository at this point in the history
Previous distributions of this package included two packages -- ldclient
and testing. This top level testing namespace can conflict with other
packages. In fact, it conflicts with our own eventsource library.

In general this doesn't matter, but it may if:

1. You are using a build process that warns about conflicts (see [this
   issue][1])
2. You want to install the sdist on an unsupported platform and would
   like to be able to verify the tests.

To resolve this issue, we are moving the testing folder into the
ldclient package. These testing files will only be included in the sdist
format. This allows for a smaller wheel size while also allowing for
flexibility with consumers.

[1]: #281
  • Loading branch information
keelerm84 committed Apr 4, 2024
1 parent 1ddf148 commit 6a71a0a
Show file tree
Hide file tree
Showing 69 changed files with 105 additions and 105 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test: install
.PHONY: lint
lint: #! Run type analysis and linting checks
lint: install
@poetry run mypy ldclient testing
@poetry run mypy ldclient

#
# Documentation generation
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ldclient.interfaces import FeatureStore
from ldclient.versioned_data_kind import FEATURES

from testing.builders import *
from ldclient.testing.builders import *

from abc import abstractmethod
import pytest
Expand All @@ -21,11 +21,11 @@ def create_feature_store(self) -> FeatureStore:
class StoreTestScope:
def __init__(self, store: FeatureStore):
self.__store = store

@property
def store(self) -> FeatureStore:
return self.__store

# These magic methods allow the scope to be automatically cleaned up in a "with" block
def __enter__(self):
return self.__store
Expand Down Expand Up @@ -59,7 +59,7 @@ def inited_store(self, tester):
}
})
return scope

@staticmethod
def make_feature(key, ver):
return FlagBuilder(key).version(ver).on(True).variations(True, False).salt('abc').build()
Expand Down
2 changes: 1 addition & 1 deletion testing/http_util.py → ldclient/testing/http_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self, port, secure):
self.server = HTTPServer(('localhost', port), MockServerRequestHandler)
if secure:
context = SSLContext(PROTOCOL_TLSv1_2)
context.load_cert_chain('./testing/selfsigned.pem', './testing/selfsigned.key')
context.load_cert_chain('./ldclient/testing/selfsigned.pem', './ldclient/testing/selfsigned.key')
self.server.socket = context.wrap_socket(
self.server.socket,
server_side=True
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from ldclient.impl.datasource.feature_requester import FeatureRequesterImpl
from ldclient.version import VERSION
from ldclient.versioned_data_kind import FEATURES, SEGMENTS
from testing.http_util import start_server, BasicResponse, JsonResponse
from testing.proxy_test_util import do_proxy_tests
from ldclient.testing.http_util import start_server, BasicResponse, JsonResponse
from ldclient.testing.proxy_test_util import do_proxy_tests

def test_get_all_data_returns_data():
with start_server() as server:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from ldclient.interfaces import DataSourceStatus, DataSourceState, DataSourceErrorKind
from ldclient.versioned_data_kind import FEATURES, SEGMENTS

from testing.builders import *
from testing.stub_util import MockFeatureRequester, MockResponse
from testing.test_util import SpyListener
from ldclient.testing.builders import *
from ldclient.testing.stub_util import MockFeatureRequester, MockResponse
from ldclient.testing.test_util import SpyListener

pp = None
mock_requester = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
from ldclient.interfaces import DataSourceStatus, DataSourceState, DataSourceErrorKind
from ldclient.impl.datasource.status import DataSourceUpdateSinkImpl

from testing.builders import *
from testing.http_util import start_server, BasicResponse, CauseNetworkError, SequentialHandler
from testing.proxy_test_util import do_proxy_tests
from testing.stub_util import make_delete_event, make_patch_event, make_put_event, make_invalid_put_event, stream_content
from testing.test_util import SpyListener
from ldclient.testing.builders import *
from ldclient.testing.http_util import start_server, BasicResponse, CauseNetworkError, SequentialHandler
from ldclient.testing.proxy_test_util import do_proxy_tests
from ldclient.testing.stub_util import make_delete_event, make_patch_event, make_put_event, make_invalid_put_event, stream_content
from ldclient.testing.test_util import SpyListener

brief_delay = 0.001

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from ldclient.impl.evaluator import Evaluator, _make_big_segment_ref
from ldclient.impl.events.types import EventFactory
from ldclient.impl.model import *
from testing.builders import *
from ldclient.testing.builders import *

from typing import Any, Optional, Tuple, Union

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ldclient.evaluation import EvaluationDetail
from ldclient.impl.events.types import EventFactory

from testing.builders import *
from ldclient.testing.builders import *

_event_factory_default = EventFactory(False)
_user = Context.create('x')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
from ldclient.impl.util import timedelta_millis
from ldclient.impl.events.event_context_formatter import EventContextFormatter

from testing.builders import *
from testing.proxy_test_util import do_proxy_tests
from testing.stub_util import MockHttp
from ldclient.testing.builders import *
from ldclient.testing.proxy_test_util import do_proxy_tests
from ldclient.testing.stub_util import MockHttp


default_config = Config("fake_sdk_key")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ldclient.impl.events.event_summarizer import EventSummarizer, EventSummaryCounter, EventSummaryFlag
from ldclient.impl.events.types import *

from testing.builders import *
from ldclient.testing.builders import *


user = Context.create('user1')
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ldclient.evaluation import BigSegmentsStatus
from ldclient.impl.big_segments import BigSegmentStoreManager, _hash_for_user_key
from ldclient.interfaces import BigSegmentStoreMetadata
from testing.mock_components import MockBigSegmentStore
from ldclient.testing.mock_components import MockBigSegmentStore

from queue import Queue
import time
Expand Down Expand Up @@ -108,20 +108,20 @@ def test_membership_query_least_recent_context_evicted_from_cache():
result3 = manager.get_user_membership(user_key_3)

assert store.membership_queries == [user_hash_1, user_hash_2, user_hash_3]

# Since the capacity is only 2 and user_key_1 was the least recently used, that key should be
# evicted by the user_key_3 query. Now only user_key_2 and user_key_3 are in the cache, and
# querying them again should not cause a new query to the store.
result2a = manager.get_user_membership(user_key_2)
result3a = manager.get_user_membership(user_key_3)
assert result2a == result2
assert result3a == result3

assert store.membership_queries == [user_hash_1, user_hash_2, user_hash_3]

result1a = manager.get_user_membership(user_key_1)
assert result1a == result1

assert store.membership_queries == [user_hash_1, user_hash_2, user_hash_3, user_hash_1]
finally:
manager.stop()
Expand All @@ -130,7 +130,7 @@ def test_status_polling_detects_store_unavailability():
store = MockBigSegmentStore()
store.setup_metadata_always_up_to_date()
statuses = Queue()

manager = BigSegmentStoreManager(BigSegmentsConfig(store=store, status_poll_interval=0.01))

try:
Expand All @@ -155,7 +155,7 @@ def test_status_polling_detects_stale_status():
store = MockBigSegmentStore()
store.setup_metadata_always_up_to_date()
statuses = Queue()

manager = BigSegmentStoreManager(BigSegmentsConfig(store=store, status_poll_interval=0.01))

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from ldclient.impl.listeners import Listeners
from ldclient.versioned_data_kind import FEATURES, SEGMENTS

from testing.test_util import SpyListener
from testing.builders import FlagBuilder, FlagRuleBuilder, make_clause, SegmentBuilder, SegmentRuleBuilder
from ldclient.testing.test_util import SpyListener
from ldclient.testing.builders import FlagBuilder, FlagRuleBuilder, make_clause, SegmentBuilder, SegmentRuleBuilder


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ldclient.client import Context
from ldclient.evaluation import EvaluationDetail
from testing.builders import *
from testing.impl.evaluator_util import *
from ldclient.testing.builders import *
from ldclient.testing.impl.evaluator_util import *


def test_flag_returns_off_variation_if_flag_is_off():
Expand Down Expand Up @@ -100,5 +100,5 @@ def test_segment_match_clause_falls_through_with_no_errors_if_segment_not_found(
user = Context.create('foo')
flag = make_boolean_flag_with_clauses(make_clause_matching_segment_key('segkey'))
evaluator = EvaluatorBuilder().with_unknown_segment('segkey').build()

assert evaluator.evaluate(flag, user, event_factory).detail.value == False
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import pytest

from ldclient.evaluation import BigSegmentsStatus
from testing.builders import *
from testing.impl.evaluator_util import *
from ldclient.testing.builders import *
from ldclient.testing.impl.evaluator_util import *


def test_big_segment_with_no_generation_is_not_matched():
Expand Down Expand Up @@ -30,7 +30,7 @@ def _test_matched_with_include(non_default_kind: bool, multi_kind_context: bool)
single_kind_context = Context.create(target_key, 'kind1') if non_default_kind else Context.create(target_key)
eval_context = Context.create_multi(single_kind_context, Context.create('key2', 'kind2')) if multi_kind_context \
else single_kind_context

segment = SegmentBuilder('key').version(1) \
.unbounded(True) \
.unbounded_context_kind('kind1' if non_default_kind else None) \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from ldclient.impl.evaluator import _bucket_context, _variation_index_for_context
from ldclient.impl.model import *

from testing.builders import *
from testing.impl.evaluator_util import *
from ldclient.testing.builders import *
from ldclient.testing.impl.evaluator_util import *

import math
import pytest
Expand All @@ -22,7 +22,7 @@ def test_variation_index_is_returned_for_bucket(self):
# so we can construct a rollout whose second bucket just barely contains that value
bucket_value = math.trunc(_bucket_context(None, user, None, flag.key, flag.salt, None) * 100000)
assert bucket_value > 0 and bucket_value < 100000

bad_variation_a = 0
matched_variation = 1
bad_variation_b = 2
Expand All @@ -44,7 +44,7 @@ def test_last_bucket_is_used_if_bucket_value_equals_total_weight(self):

# We'll construct a list of variations that stops right at the target bucket value
bucket_value = math.trunc(_bucket_context(None, user, None, flag.key, flag.salt, None) * 100000)

rule = VariationOrRollout({
'rollout': {
'variations': [
Expand All @@ -54,7 +54,7 @@ def test_last_bucket_is_used_if_bucket_value_equals_total_weight(self):
})
result_variation = _variation_index_for_context(flag, rule, user)
assert result_variation == (0, False)

def test_bucket_by_user_key(self):
user = Context.create('userKeyA')
bucket = _bucket_context(None, user, None, 'hashKey', 'saltyA', None)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ldclient.client import Context
from testing.builders import *
from testing.impl.evaluator_util import *
from ldclient.testing.builders import *
from ldclient.testing.impl.evaluator_util import *


def assert_match_clause(clause: dict, context: Context, should_match: bool):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from ldclient.evaluation import EvaluationDetail
from ldclient.impl.events.types import EventInputEvaluation

from testing.builders import *
from testing.impl.evaluator_util import *
from ldclient.testing.builders import *
from ldclient.testing.impl.evaluator_util import *


def test_flag_returns_off_variation_if_prerequisite_not_found():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from ldclient import Context
from ldclient.impl.evaluator import _bucket_context
from testing.builders import *
from testing.impl.evaluator_util import *
from ldclient.testing.builders import *
from ldclient.testing.impl.evaluator_util import *


def _segment_matches_context(segment: Segment, context: Context) -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ldclient.client import Context
from testing.builders import *
from testing.impl.evaluator_util import *
from ldclient.testing.builders import *
from ldclient.testing.impl.evaluator_util import *


FALLTHROUGH_VAR = 0
Expand Down Expand Up @@ -34,7 +34,7 @@ def test_user_targets_only(self):
.target(MATCH_VAR_1, 'c') \
.target(MATCH_VAR_2, 'b', 'a') \
.build()

expect_match(flag, Context.create('a'), MATCH_VAR_2)
expect_match(flag, Context.create('b'), MATCH_VAR_2)
expect_match(flag, Context.create('c'), MATCH_VAR_1)
Expand All @@ -61,7 +61,7 @@ def test_user_targets_and_context_targets(self):
.context_target(Context.DEFAULT_KIND, MATCH_VAR_1) \
.context_target(Context.DEFAULT_KIND, MATCH_VAR_2) \
.build()

expect_match(flag, Context.create('a'), MATCH_VAR_2)
expect_match(flag, Context.create('b'), MATCH_VAR_2)
expect_match(flag, Context.create('c'), MATCH_VAR_1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ldclient.impl.flag_tracker import FlagTrackerImpl
from testing.test_util import SpyListener
from ldclient.testing.test_util import SpyListener
from ldclient.impl.listeners import Listeners
from ldclient.interfaces import FlagChange

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ldclient.impl.model import *

from testing.builders import *
from ldclient.testing.builders import *


def test_flag_targets_are_stored_as_sets():
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ldclient.impl import operators

from testing.builders import *
from ldclient.testing.builders import *


@pytest.mark.parametrize("op,context_value,clause_value,expected", [
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from ldclient.interfaces import FeatureStore
from ldclient.versioned_data_kind import FEATURES

from testing.feature_store_test_base import FeatureStoreTestBase, FeatureStoreTester, StoreTestScope
from testing.test_util import skip_database_tests
from ldclient.testing.feature_store_test_base import FeatureStoreTestBase, FeatureStoreTester, StoreTestScope
from ldclient.testing.test_util import skip_database_tests


# The standard test suite to be run against all persistent feature store implementations. See
# testing.feature_store_test_base for the basic model being used here. For each database integration,
# ldclient.testing.feature_store_test_base for the basic model being used here. For each database integration,
# we must define a subclass of PersistentFeatureStoreTester which overrides its abstract methods as
# appropriate for that database, and then define a subclass of PersistentFeatureStoreTestBase which
# simply specifies what tester subclass to use.
Expand All @@ -29,7 +29,7 @@ def create_persistent_feature_store(self, prefix: str, caching: CacheConfig) ->
:param caching: caching parameters for the store constructor
"""
pass

@abstractmethod
def clear_data(self, prefix: str):
"""
Expand Down Expand Up @@ -74,12 +74,12 @@ def test_stores_with_different_prefixes_are_independent(self):
tester_b = self.tester_class()
tester_b.prefix = "b"
tester_b.clear_data(tester_b.prefix)

flag_a1 = { 'key': 'flagA1', 'version': 1 }
flag_a2 = { 'key': 'flagA2', 'version': 1 }
flag_b1 = { 'key': 'flagB1', 'version': 1 }
flag_b2 = { 'key': 'flagB2', 'version': 1 }

with StoreTestScope(tester_a.create_feature_store()) as store_a:
with StoreTestScope(tester_b.create_feature_store()) as store_b:
store_a.init({ FEATURES: { 'flagA1': flag_a1 } })
Expand Down
Loading

0 comments on commit 6a71a0a

Please sign in to comment.