111 lines
5.3 KiB
Python
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()
|