Download tests/test_reader_pdf_loading.py from VoiceOfML/Search: direct link, hf CLI and curl.
- Browser
- Download file 18 kB
-
https://huggingface.co/spaces/VoiceOfML/Search/resolve/main/tests/test_reader_pdf_loading.py
- Command line
-
hf download hf://spaces/VoiceOfML/Search/tests/test_reader_pdf_loading.py
-
curl -L -o test_reader_pdf_loading.py https://huggingface.co/spaces/VoiceOfML/Search/resolve/main/tests/test_reader_pdf_loading.py
18 kB
| """Actual module imports and PDF.js with cold cache and transport faults.""" | |
| import contextlib | |
| import json | |
| import re | |
| import threading | |
| import time | |
| import unittest | |
| import urllib.parse | |
| from unittest.mock import patch | |
| from tests.test_reader_performance import ( | |
| PlaywrightError, StaticHandler, minimal_pdf, static_server, sync_playwright, | |
| ) | |
| MODULE = '**/static/vendor/pdf.min.*.mjs*' | |
| SOURCE = 'https://huggingface.co/datasets/VoiceOfML/Test/resolve/main/cold-retry.pdf' | |
| class PdfLoadingTests(unittest.TestCase): | |
| def setUpClass(cls): | |
| resources = contextlib.ExitStack() | |
| cls.addClassCleanup(resources.close) | |
| cls.origin = resources.enter_context(static_server()) | |
| playwright = resources.enter_context(sync_playwright()) | |
| try: | |
| cls.browser = playwright.chromium.launch(headless=True, args=['--no-sandbox']) | |
| except PlaywrightError as error: | |
| raise unittest.SkipTest(str(error)) | |
| cls.addClassCleanup(cls.browser.close) | |
| def setUp(self): | |
| self.context = self.browser.new_context(service_workers='block') | |
| self.addCleanup(self.context.close) | |
| self.page = self.context.new_page() | |
| self.page.set_default_timeout(10000) | |
| self.errors, self.modules, self.documents = [], [], [] | |
| self.page.on('pageerror', lambda error: self.errors.append(str(error))) | |
| self.page.on('request', lambda request: self.modules.append(request.url) | |
| if '/vendor/pdf.min.' in request.url else None) | |
| self.page.route('**/api/reader-content**', self.document) | |
| cdp = self.context.new_cdp_session(self.page) | |
| cdp.send('Network.enable') | |
| cdp.send('Network.setCacheDisabled', {'cacheDisabled': True}) | |
| def document(self, route): | |
| self.documents.append(route.request.url) | |
| route.fulfill(content_type='application/pdf', body=minimal_pdf()) | |
| def open(self): | |
| self.page.goto(self.origin + '/static/reader.html?' + urllib.parse.urlencode( | |
| dict(url=SOURCE, ext='pdf', title='Cold retry')), wait_until='domcontentloaded') | |
| def ready(self): | |
| self.page.locator('.reader-page canvas.ready').wait_for(state='attached') | |
| self.assertEqual(self.page.locator('#status').text_content(), '1 \u9875') | |
| self.assertEqual(self.errors, []) | |
| def test_cold_success_uses_one_module_request(self): | |
| self.open() | |
| self.ready() | |
| self.assertEqual(len(self.modules), 1) | |
| self.assertNotIn('reader-module-retry', self.modules[0]) | |
| def test_pdf_engine_preloads_before_id_resolution(self): | |
| pending = [] | |
| self.page.route('**/api/reader-resolve?**', lambda route: pending.append(route)) | |
| with self.page.expect_worker() as opened: | |
| self.page.goto(self.origin + '/static/reader.html?id=398vk0yyy8m29&ext=pdf', wait_until='domcontentloaded') | |
| worker = opened.value | |
| worker.evaluate('''() => new Promise((resolve, reject) => { | |
| const deadline = setTimeout(() => reject(Error('Worker did not initialize')), 5000); | |
| const check = () => globalThis.pdfjsWorker ? (clearTimeout(deadline), resolve(true)) : setTimeout(check, 10); | |
| check(); | |
| })''') | |
| self.assertEqual(len(pending), 1) | |
| self.assertEqual(self.documents, []) | |
| pending[0].fulfill(content_type='application/json', body=json.dumps({ | |
| 'url': SOURCE, 'download': SOURCE, 'extension': 'pdf', | |
| })) | |
| self.ready() | |
| self.assertEqual(len(self.page.workers), 1) | |
| self.assertEqual(len(pending), 1) | |
| def test_early_resolve_runs_before_main_module_and_retries_transient_http_once(self): | |
| held, lookups = [], [] | |
| self.page.route('**/static/reader.js*', lambda route: held.append(route)) | |
| def lookup(route): | |
| lookups.append(route) | |
| if len(lookups) == 1: | |
| route.fulfill(status=503, body='try again') | |
| else: | |
| route.fulfill(content_type='application/json', body=json.dumps({ | |
| 'url': SOURCE, 'download': SOURCE, 'extension': 'pdf', | |
| })) | |
| self.page.route('**/api/reader-resolve?**', lookup) | |
| self.page.goto(self.origin + '/static/reader.html?id=398vk0yyy8m29&ext=pdf', wait_until='commit') | |
| self.page.wait_for_function('() => !!window.__VOICE_READER_RESOLVE__') | |
| self.assertEqual(self.page.evaluate('() => window.__VOICE_READER_RESOLVE__.promise.then(x => x.status)'), 503) | |
| self.assertEqual(self.documents, []) | |
| self.page.unroute('**/static/reader.js*') | |
| for route in held: route.continue_() | |
| self.ready() | |
| self.assertEqual(len(lookups), 2) | |
| def test_disposal_terminates_prepared_worker_and_pending_resolve(self): | |
| self.page.add_init_script('''window.__lookupAborted = false; | |
| const nativeFetch = window.fetch; | |
| window.fetch = (url, options) => String(url).includes('/api/reader-resolve?') | |
| ? new Promise((resolve, reject) => options.signal.addEventListener('abort', () => { | |
| window.__lookupAborted = true; reject(new DOMException('closed', 'AbortError')); | |
| }, {once:true})) : nativeFetch(url, options);''') | |
| with self.page.expect_worker() as opened: | |
| self.page.goto(self.origin + '/static/reader.html?id=398vk0yyy8m29&ext=pdf', wait_until='domcontentloaded') | |
| with opened.value.expect_event('close'): | |
| self.page.evaluate("() => window.dispatchEvent(new Event('pagehide'))") | |
| self.assertTrue(self.page.evaluate('() => window.__lookupAborted')) | |
| self.assertEqual(self.documents, []) | |
| self.assertEqual(self.errors, []) | |
| def test_cached_resolution_skips_early_network_lookup(self): | |
| self.page.add_init_script('sessionStorage.setItem("reader-resolve:398vk0yyy8m29", ' + | |
| json.dumps(json.dumps({'url': SOURCE, 'download': SOURCE, 'extension': 'pdf'})) + ');') | |
| lookups = [] | |
| self.page.route('**/api/reader-resolve?**', lambda route: (lookups.append(route.request.url), route.abort())) | |
| self.page.goto(self.origin + '/static/reader.html?id=398vk0yyy8m29&ext=pdf', wait_until='domcontentloaded') | |
| self.ready() | |
| self.assertEqual(lookups, []) | |
| def test_worker_timeout_terminates_once_and_exposes_retry(self): | |
| self.page.add_init_script('''const native = window.setTimeout.bind(window); | |
| window.setTimeout = (fn, ms, ...args) => native(fn, ms === 20000 ? 250 : ms, ...args);''') | |
| self.page.route(MODULE, lambda route: route.fulfill(content_type='text/javascript', body=''' | |
| export const GlobalWorkerOptions = {}; | |
| export class PDFWorker { | |
| promise = new Promise(() => {}); | |
| destroy() { window.__workerDestroys = (window.__workerDestroys || 0) + 1; } | |
| } | |
| export function getDocument() { throw Error('must not fetch before Worker readiness'); } | |
| ''')) | |
| self.open() | |
| self.page.locator('#reader-engine-retry').wait_for() | |
| self.assertEqual(self.page.evaluate('() => window.__workerDestroys'), 1) | |
| self.assertEqual(self.documents, []) | |
| self.assertEqual(self.errors, []) | |
| def test_proxy_failure_reuses_prepared_worker_for_original_source(self): | |
| self.page.unroute('**/api/reader-content**') | |
| self.page.route('**/api/reader-content**', lambda route: route.fulfill(status=403, body='forbidden')) | |
| self.page.route(SOURCE, self.document) | |
| self.open() | |
| self.ready() | |
| self.assertEqual(self.documents, [SOURCE]) | |
| self.assertEqual(len(self.page.workers), 1) | |
| def test_resolved_non_pdf_releases_speculative_worker(self): | |
| pending = [] | |
| self.page.route('**/api/reader-resolve?**', lambda route: pending.append(route)) | |
| self.page.unroute('**/api/reader-content**') | |
| self.page.route('**/api/reader-content**', lambda route: route.fulfill(content_type='text/plain', body='Resolved text')) | |
| with self.page.expect_worker() as opened: | |
| self.page.goto(self.origin + '/static/reader.html?id=398vk0yyy8m29&ext=pdf', wait_until='domcontentloaded') | |
| with opened.value.expect_event('close'): | |
| pending[0].fulfill(content_type='application/json', body=json.dumps({ | |
| 'url': SOURCE.replace('.pdf', '.txt'), 'extension': 'txt', | |
| })) | |
| self.page.locator('#content').get_by_text('Resolved text').wait_for() | |
| self.assertEqual(self.errors, []) | |
| def test_large_original_pdf_renders_using_bounded_ranges_and_stops_transfer(self): | |
| # A valid large stream object before the xref, not padding after EOF. | |
| small = minimal_pdf() | |
| xref = small.index(b'xref\n') | |
| attachment = b'x' * (4 * 1024 * 1024) | |
| extra = b'6 0 obj\n<< /Length ' + str(len(attachment)).encode() + b' >>\nstream\n' + attachment + b'\nendstream\nendobj\n' | |
| suffix = small[xref:].replace(b'0 6\n', b'0 7\n').replace(b'trailer\n', f'{xref:010d} 00000 n \ntrailer\n'.encode()).replace(b'/Size 6', b'/Size 7') | |
| suffix = re.sub(rb'startxref\n\d+', b'startxref\n' + str(xref + len(extra)).encode(), suffix) | |
| payload = small[:xref] + extra + suffix | |
| requests, stopped = [], threading.Event() | |
| original = StaticHandler.do_GET | |
| def serve(handler): | |
| if not handler.path.startswith('/range-fixture.pdf'): | |
| return original(handler) | |
| match = re.fullmatch(r'bytes=(\d+)-(\d*)', handler.headers.get('Range', '')) | |
| start, end = (int(match[1]), min(int(match[2] or len(payload)-1), len(payload)-1)) if match else (0, len(payload)-1) | |
| request = {'range': (start, end) if match else None, 'sent': 0} | |
| requests.append(request) | |
| handler.send_response(206 if match else 200) | |
| handler.send_header('Content-Type', 'application/pdf') | |
| handler.send_header('Content-Length', str(end-start+1)) | |
| handler.send_header('Accept-Ranges', 'bytes') | |
| if match: | |
| handler.send_header('Content-Range', f'bytes {start}-{end}/{len(payload)}') | |
| handler.end_headers() | |
| try: | |
| for offset in range(start, end+1, 16384): | |
| chunk = payload[offset:min(offset+16384, end+1)] | |
| handler.wfile.write(chunk) | |
| handler.wfile.flush() | |
| request['sent'] += len(chunk) | |
| time.sleep(.005) | |
| except (BrokenPipeError, ConnectionResetError): | |
| pass | |
| finally: | |
| if not match: stopped.set() | |
| self.page.unroute('**/api/reader-content**') | |
| self.page.add_init_script('''const originalFetch = window.fetch; | |
| window.fetch = (url, init) => originalFetch(String(url).includes('/api/reader-content?') ? '/range-fixture.pdf' : url, init);''') | |
| with patch.object(StaticHandler, 'do_GET', serve): | |
| self.open() | |
| self.ready() | |
| self.assertTrue(stopped.wait(2), 'full-file transfer must be cancelled') | |
| ranges = [item['range'] for item in requests if item['range']] | |
| self.assertIn((0, 262143), ranges) | |
| self.assertTrue(any(end == len(payload)-1 for _, end in ranges)) | |
| self.assertLess(sum(item['sent'] for item in requests), 1024 * 1024) | |
| self.assertEqual(len(self.modules), 1) | |
| def test_published_range_artifact_selects_small_chunk(self): | |
| source = 'https://huggingface.co/datasets/vomebook/Reader-Assets/resolve/main/' \ | |
| 'objects/aa/' + 'a' * 64 + '/pdf-range-v1-0123456789abcdef-objects/document.pdf' | |
| self.page.route(MODULE, lambda route: route.fulfill(content_type='text/javascript', body=''' | |
| export const GlobalWorkerOptions = {}; | |
| export class PDFWorker { promise = Promise.resolve(); destroy() {} } | |
| export function getDocument(options) { | |
| window.__pdfRangeChunk = options.rangeChunkSize; | |
| const page = { | |
| getViewport: ({scale}) => ({width: 600 * scale, height: 800 * scale, | |
| convertToViewportPoint: (x, y) => [x, y]}), | |
| getTextContent: async () => ({items: []}), | |
| render: () => ({promise: Promise.resolve()}), | |
| }; | |
| return {promise: Promise.resolve({numPages: 1, | |
| getPage: async () => page, getOutline: async () => null}), | |
| destroy: () => Promise.resolve()}; | |
| } | |
| ''')) | |
| self.page.goto(self.origin + '/static/reader.html?' + urllib.parse.urlencode( | |
| dict(url=source, ext='pdf', title='Optimized PDF')), wait_until='domcontentloaded') | |
| self.ready() | |
| self.assertEqual(self.page.evaluate('() => window.__pdfRangeChunk'), 262144) | |
| def test_first_connection_failure_recovers_with_real_pdf_engine(self): | |
| def serve(route): | |
| if 'reader-module-retry' not in route.request.url: | |
| route.abort('connectionreset') | |
| else: | |
| route.continue_() | |
| self.page.route(MODULE, serve) | |
| self.open() | |
| self.ready() | |
| self.assertEqual(len(self.modules), 2) | |
| self.assertIn('reader-module-retry=1', self.modules[1]) | |
| self.assertEqual(len(self.documents), 1) | |
| def test_http_failure_recovers_on_last_attempt(self): | |
| self.page.route(MODULE, lambda route: route.continue_() | |
| if 'reader-module-retry=2' in route.request.url | |
| else route.fulfill(status=503, body='temporarily unavailable')) | |
| self.open() | |
| self.ready() | |
| self.assertEqual(len(self.modules), 3) | |
| def test_exhaustion_has_bounded_requests_and_manual_reload_recovers(self): | |
| self.page.route(MODULE, lambda route: route.abort('connectionreset')) | |
| self.open() | |
| self.page.locator('#reader-engine-retry').wait_for() | |
| self.assertEqual(len(self.modules), 3) | |
| self.assertEqual(self.documents, []) | |
| self.assertEqual(self.page.locator('#content').get_attribute('data-error-code'), 'READER_ENGINE_NETWORK') | |
| self.assertNotIn('Failed to fetch', self.page.locator('.reader-error').inner_text()) | |
| self.page.unroute(MODULE) | |
| self.page.locator('#reader-engine-retry').click() | |
| self.ready() | |
| self.assertEqual(len(self.documents), 1) | |
| def test_disposal_cancels_retry_backoff(self): | |
| self.page.route(MODULE, lambda route: route.abort('connectionreset')) | |
| self.open() | |
| self.page.wait_for_timeout(100) | |
| self.page.evaluate("() => window.dispatchEvent(new Event('pagehide'))") | |
| self.page.wait_for_timeout(1800) | |
| self.assertEqual(len(self.modules), 1) | |
| self.assertEqual(self.documents, []) | |
| self.assertEqual(self.errors, []) | |
| def test_timed_out_import_cannot_start_document_after_disposal(self): | |
| held = [] | |
| self.page.add_init_script('''const native = window.setTimeout.bind(window); | |
| window.setTimeout = (fn, ms, ...args) => native(fn, ms === 20000 ? 50 : ms, ...args);''') | |
| self.page.route(MODULE, lambda route: held.append(route)) | |
| self.open() | |
| self.page.locator('#reader-engine-retry').wait_for() | |
| self.assertEqual(len(self.modules), 3) | |
| for route in held: | |
| route.fulfill(content_type='text/javascript', body=''' | |
| export const GlobalWorkerOptions = {}; | |
| export function getDocument() { window.__latePdfStarted=true; throw Error('late'); } | |
| ''') | |
| self.page.wait_for_timeout(200) | |
| self.assertFalse(self.page.evaluate('() => !!window.__latePdfStarted')) | |
| self.assertEqual(self.documents, []) | |
| self.assertEqual(self.errors, []) | |
| def test_syntax_error_is_not_retried_as_network_failure(self): | |
| self.page.route(MODULE, lambda route: route.fulfill(content_type='text/javascript', body='export const = ;')) | |
| self.open() | |
| self.page.locator('.reader-error').wait_for() | |
| self.assertEqual(len(self.modules), 1) | |
| self.assertEqual(self.page.locator('#reader-engine-retry').count(), 0) | |
| def test_first_http_failure_recovers_under_real_service_worker(self): | |
| original = StaticHandler.do_GET | |
| requests = [] | |
| def serve(handler): | |
| if '/vendor/pdf.min.' in handler.path: | |
| requests.append(handler.path) | |
| if len(requests) == 1: | |
| handler.send_error(503) | |
| return | |
| if handler.path == '/manifest.json' or handler.path.startswith('/icons/'): | |
| handler.path = '/static' + handler.path | |
| original(handler) | |
| with patch.object(StaticHandler, 'do_GET', serve), \ | |
| self.browser.new_context(service_workers='allow') as context: | |
| page = context.new_page() | |
| context.route('**/api/reader-content**', lambda route: route.fulfill( | |
| content_type='application/pdf', body=minimal_pdf())) | |
| page.goto(self.origin + '/static/manifest.json') | |
| page.evaluate('''async () => { | |
| await navigator.serviceWorker.register('/static/sw.js',{scope:'/static/'}); | |
| await Promise.race([navigator.serviceWorker.ready, | |
| new Promise((_, reject) => setTimeout(() => reject(Error('SW activation timed out')), 10000))]); | |
| }''') | |
| page.goto(self.origin + '/static/reader.html?' + urllib.parse.urlencode( | |
| dict(url=SOURCE, ext='pdf', title='Cold SW retry'))) | |
| page.locator('.reader-page canvas.ready').wait_for(state='attached', timeout=15000) | |
| self.assertTrue(page.evaluate('() => !!navigator.serviceWorker.controller')) | |
| self.assertEqual(len(requests), 2) | |
| self.assertIn('reader-module-retry=1', requests[1]) | |