Files
reelbox/server/tests/test_catalog_performance.py
T

111 lines
5.3 KiB
Python

"""Operation-count regressions; timings are recorded separately, never asserted."""
import json
import threading
import unittest
from unittest.mock import patch
from urllib.request import Request, urlopen
from urllib.error import HTTPError
import test_catalog_api as fixtures
class CatalogPerformanceTests(unittest.TestCase):
setUp = fixtures.CatalogTests.setUp
tearDown = fixtures.CatalogTests.tearDown
open = fixtures.CatalogTests.open
def http_server(self, catalog):
server = self.api.make_server(catalog, port=0)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
def stop():
server.shutdown()
server.server_close()
thread.join(3)
self.addCleanup(stop)
return 'http://127.0.0.1:' + str(server.server_port)
def test_repeated_snapshot_http_read_does_not_query_or_serialize_again(self):
catalog = self.open()
origin = self.http_server(catalog)
with urlopen(origin + '/api/first/public/', timeout=3) as response:
first = response.read()
queries = []
catalog.db.set_trace_callback(queries.append)
with patch.object(self.api.json, 'dumps', wraps=json.dumps) as encode:
with urlopen(origin + '/api/first/public/', timeout=3) as response:
self.assertEqual(response.read(), first)
self.assertEqual(queries, [], 'Warm snapshot HTTP responses must reuse encoded bytes')
self.assertEqual(encode.call_count, 0)
def test_cache_bounds_lru_and_large_response_bypass(self):
catalog = self.open()
catalog.RESPONSE_CACHE_ENTRIES = 2
one = '/api/movie/by/1/public/'
two = '/api/movie/by/2/public/'
three = '/api/movie/by/3/public/'
first = catalog.response(one)
catalog.response(two)
self.assertIs(catalog.response(one), first)
catalog.response(three)
self.assertEqual(list(catalog._responses), [one, three])
self.assertLessEqual(catalog._response_bytes, catalog.RESPONSE_CACHE_BYTES)
catalog.RESPONSE_CACHE_ITEM_BYTES = 1
catalog.response(two)
self.assertNotIn(two, catalog._responses)
# Byte budget, not just number of entries, forces eviction.
catalog.RESPONSE_CACHE_ITEM_BYTES = 1024 * 1024
catalog.RESPONSE_CACHE_BYTES = len(catalog.response(three)) + len(three) + 1
catalog.response(two)
self.assertLessEqual(catalog._response_bytes, catalog.RESPONSE_CACHE_BYTES)
self.assertEqual(len(catalog._responses), 1)
def test_dynamic_responses_errors_and_queries_are_never_retained(self):
catalog = self.open()
with patch.object(catalog, 'health', side_effect=[{'age': 1}, {'age': 2}]):
self.assertNotEqual(catalog.response('/api/health'), catalog.response('/api/health'))
with patch.object(self.api, 'load_banners', side_effect=[{'banners': [1]}, {'banners': [2]}]):
self.assertNotEqual(catalog.response('/api/banners'), catalog.response('/api/banners'))
catalog.response('/api-user/newapi/like.php?action=search-movie&q=Alpha')
for _ in range(2):
with self.assertRaises(self.api.ApiError):
catalog.response('/api/movie/by/999/public/')
self.assertEqual(list(catalog._responses), [])
def test_warm_head_reuses_get_bytes_and_body_requests_still_fail(self):
catalog = self.open()
origin = self.http_server(catalog)
with urlopen(origin + '/api/first/public/', timeout=3) as response:
first = response.read()
with urlopen(Request(origin + '/api/first/public/', method='HEAD'), timeout=3) as response:
self.assertEqual(response.read(), b'')
self.assertEqual(int(response.headers['Content-Length']), len(first))
with self.assertRaises(HTTPError) as error:
urlopen(Request(origin + '/api/first/public/', method='GET', data=b'x'), timeout=3)
self.assertEqual(error.exception.code, 400)
error.exception.close()
def test_normalized_search_matches_fallback_with_unicode_and_pagination(self):
catalog = self.open()
indexed = catalog.search_text
self.assertLessEqual(catalog.search_index_bytes, 64 * 1024 * 1024)
for query in ['Actor', 'بازیگران', 'بازيگران', 'ALPHA', 'not-present', 'Film 4']:
for page in [0, 1, 2]:
catalog.search_text = indexed
expected = catalog.titles(query=query, page=page)
catalog.search_text = {}
self.assertEqual(catalog.titles(query=query, page=page), expected)
catalog.search_text = indexed
def test_search_normalizes_query_not_every_snapshot_row(self):
catalog = self.open()
with patch.object(self.api, 'normal', wraps=self.api.normal) as normalize:
catalog.db.create_function('normal', 1, normalize, deterministic=True)
result = catalog.dispatch('/api-user/newapi/like.php?action=search-movie&q=ACTOR&pageno=1')
self.assertEqual([row['id'] for row in result], list(range(62, 32, -1)))
self.assertLessEqual(normalize.call_count, 2,
'Immutable title text must not be normalized on every search')
if __name__ == '__main__':
unittest.main()