Skip to content

Commit

Permalink
Merge c5292d7 into c19be4b
Browse files Browse the repository at this point in the history
  • Loading branch information
alisaifee committed May 18, 2020
2 parents c19be4b + c5292d7 commit abd6079
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 46 deletions.
11 changes: 9 additions & 2 deletions .github/workflows/main.yml
Expand Up @@ -12,6 +12,12 @@ jobs:
strategy:
matrix:
python-version: [2.7, pypy2, 3.5, 3.7, 3.8, pypy3]
include:
- python-version: 2.7
marker: 'integration'
- python-version: 3.7
coverage: true
marker: 'unit,integration'
steps:
- uses: actions/checkout@v2
- name: Cache dependencies
Expand All @@ -34,10 +40,11 @@ jobs:
make setup-test-backends
- name: Tests
run: |
nosetests tests --with-cov -v --with-timer --timer-top-n 10
- name: Coverage
py.test -m ${{ matrix.marker || 'unit' }}
- name: Post Coverage
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: |
coveralls
if: ${{ matrix.coverage }}

8 changes: 7 additions & 1 deletion Makefile
Expand Up @@ -53,5 +53,11 @@ osx-hacks: redis-uds-stop memcached-uds-stop redis-uds-start memcached-uds-start
setup-test-backends: $(OS_HACKS) memcached-gae-install docker-up

tests: setup-test-backends
nosetests tests --with-cov -v --with-timer --timer-top-n 10
pytest -m unit --durations=10

integration-tests: setup-test-backends
pytest -m integration

all-tests: setup-test-backends
pytest -m unit,integration --durations=10
.PHONY: test
12 changes: 12 additions & 0 deletions pytest.ini
@@ -0,0 +1,12 @@
[pytest]
norecursedirs = google google_appengine build *.egg
markers =
unit: mark a test as a unit test.
integration: mark a test as an integration test.
addopts =
--verbose
--tb=short
--capture=no
-rfEsxX
--cov=limits
-m unit
7 changes: 3 additions & 4 deletions requirements/test.txt
@@ -1,11 +1,10 @@
-r main.txt
nose
mock
coverage
nose-cov
pytest
pytest-cov
coverage<5
pymemcache
redis<3.1.0
redis-py-cluster>=1.1.0
PyYAML
hiro>0.1.6
nose-timer
35 changes: 8 additions & 27 deletions tests/__init__.py
@@ -1,36 +1,17 @@
import platform
import sys
from functools import wraps

from nose.plugins.skip import SkipTest
import unittest

RUN_GAE = (
sys.version_info[:2] == (2, 7)
and platform.python_implementation() == 'CPython'
sys.version_info[:2] == (2, 7)
and platform.python_implementation() == 'CPython'
)


def test_import():
import limits


def test_module_version():
import limits
assert limits.__version__ is not None


def skip_if(cond):
def _inner(fn):
@wraps(fn)
def __inner(*a, **k):
if cond() if callable(cond) else cond:
raise SkipTest
return fn(*a, **k)

return __inner

return _inner
def skip_if_pypy(fn):
return unittest.skipIf(
platform.python_implementation().lower() == 'pypy',
reason='Skipped for pypy'
)(fn)


def skip_if_pypy(fn):
return skip_if(platform.python_implementation().lower() == 'pypy')(fn)
61 changes: 61 additions & 0 deletions tests/integration/test_concurrency.py
@@ -0,0 +1,61 @@
import threading
import time
import unittest
from uuid import uuid4

import pytest

from limits.limits import RateLimitItemPerSecond
from limits.storage import MemoryStorage
from limits.strategies import FixedWindowRateLimiter, MovingWindowRateLimiter


@pytest.mark.integration
class ConcurrencyTests(unittest.TestCase):

def test_memory_storage_fixed_window(self):
storage = MemoryStorage()
limiter = FixedWindowRateLimiter(storage)
per_second = RateLimitItemPerSecond(100)

[limiter.hit(per_second, uuid4().hex) for _ in range(1000)]

key = uuid4().hex
hits = []

def hit():
if limiter.hit(per_second, key):
hits.append(None)

start = time.time()

threads = [threading.Thread(target=hit) for _ in range(1000)]
[t.start() for t in threads]
[t.join() for t in threads]

self.assertTrue(time.time() - start < 1)
self.assertEqual(len(hits), 100)

def test_memory_storage_moving_window(self):
storage = MemoryStorage()
limiter = MovingWindowRateLimiter(storage)
per_second = RateLimitItemPerSecond(100)

[limiter.hit(per_second, uuid4().hex) for _ in range(100)]

key = uuid4().hex
hits = []

def hit():
if limiter.hit(per_second, key):
hits.append(None)

start = time.time()

threads = [threading.Thread(target=hit) for _ in range(1000)]
[t.start() for t in threads]
[t.join() for t in threads]

self.assertTrue(time.time() - start < 1)
self.assertEqual(len(hits), 100)

5 changes: 4 additions & 1 deletion tests/test_limit_granularities.py
@@ -1,7 +1,10 @@
import unittest
from limits import limits

import pytest

from limits import limits

@pytest.mark.unit
class GranularityTests(unittest.TestCase):
def test_seconds_value(self):
self.assertEqual(
Expand Down
3 changes: 3 additions & 0 deletions tests/test_limits.py
@@ -1,8 +1,11 @@
import unittest

import pytest

from limits import limits


@pytest.mark.unit
class LimitsTests(unittest.TestCase):
class FakeLimit(limits.RateLimitItem):
granularity = (1, "fake")
Expand Down
27 changes: 16 additions & 11 deletions tests/test_storage.py
@@ -1,11 +1,11 @@
import threading
import time
import time
import unittest
from uuid import uuid4

import hiro
import mock
import pymemcache.client
import pytest
import redis
import redis.sentinel
import rediscluster
Expand All @@ -19,9 +19,9 @@
from limits.strategies import (
FixedWindowRateLimiter, FixedWindowElasticExpiryRateLimiter, MovingWindowRateLimiter
)
from tests import skip_if, RUN_GAE

from tests import RUN_GAE

@pytest.mark.unit
class BaseStorageTests(unittest.TestCase):
def setUp(self):
pymemcache.client.Client(('localhost', 22122)).flush_all()
Expand Down Expand Up @@ -207,6 +207,7 @@ def get_moving_window(self, *a, **k):
self.assertTrue(isinstance(storage, MyStorage))
MovingWindowRateLimiter(storage)

@pytest.mark.unit
class MemoryStorageTests(unittest.TestCase):
def setUp(self):
self.storage = MemoryStorage()
Expand Down Expand Up @@ -274,7 +275,7 @@ def test_expiry_moving_window(self):
time.sleep(0.1)
self.assertEqual([], self.storage.events[per_min.key_for()])


@pytest.mark.unit
class SharedRedisTests(object):
def test_fixed_window(self):
limiter = FixedWindowRateLimiter(self.storage)
Expand Down Expand Up @@ -326,6 +327,7 @@ def test_moving_window_expiry(self):
time.sleep(0.05)
self.assertTrue(self.storage.storage.keys("%s/*" % limit.namespace) == [])

@pytest.mark.unit
class RedisStorageTests(SharedRedisTests, unittest.TestCase):
def setUp(self):
self.storage_url = "redis://localhost:7379"
Expand All @@ -340,6 +342,7 @@ def test_init_options(self):
)


@pytest.mark.unit
class RedisUnixSocketStorageTests(SharedRedisTests, unittest.TestCase):
def setUp(self):
self.storage_url = "redis+unix:///tmp/limits.redis.sock"
Expand Down Expand Up @@ -374,6 +377,7 @@ def test_init_options(self):
)


@pytest.mark.unit
class RedisClusterStorageTests(SharedRedisTests, unittest.TestCase):
def setUp(self):
rediscluster.RedisCluster("localhost", 7000).flushall()
Expand All @@ -388,6 +392,7 @@ def test_init_options(self):
)


@pytest.mark.unit
class MemcachedStorageTests(unittest.TestCase):
def setUp(self):
pymemcache.client.Client(('localhost', 22122)).flush_all()
Expand Down Expand Up @@ -462,15 +467,15 @@ def test_clear(self):
self.assertTrue(limiter.hit(per_min))


@pytest.mark.unit
@unittest.skipUnless(RUN_GAE, reason='Only for GAE')
class GAEMemcachedStorageTests(unittest.TestCase):
def setUp(self):
if RUN_GAE:
from google.appengine.ext import testbed
tb = testbed.Testbed()
tb.activate()
tb.init_memcache_stub()
from google.appengine.ext import testbed
tb = testbed.Testbed()
tb.activate()
tb.init_memcache_stub()

@skip_if(not RUN_GAE)
def test_fixed_window(self):
storage = GAEMemcachedStorage("gaememcached://")
limiter = FixedWindowRateLimiter(storage)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_strategy.py
Expand Up @@ -3,6 +3,7 @@
import unittest

import pymemcache.client
import pytest
import redis
import redis.sentinel
import rediscluster
Expand All @@ -19,6 +20,7 @@
from tests import skip_if_pypy


@pytest.mark.unit
class WindowTests(unittest.TestCase):
def setUp(self):
pymemcache.client.Client(('localhost', 22122)).flush_all()
Expand Down

0 comments on commit abd6079

Please sign in to comment.