%PDF- %PDF-
Direktori : /lib/calibre/calibre/srv/tests/ |
Current File : //lib/calibre/calibre/srv/tests/loop.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' import ssl, os, socket, time from collections import namedtuple from unittest import skipIf from glob import glob from threading import Event from calibre.srv.pre_activated import has_preactivated_support from calibre.srv.tests.base import BaseTest, TestServer from calibre.ptempfile import TemporaryDirectory from calibre.utils.certgen import create_server_cert from calibre.utils.monotonic import monotonic from polyglot import http_client is_ci = os.environ.get('CI', '').lower() == 'true' class LoopTest(BaseTest): def test_log_rotation(self): 'Test log rotation' from calibre.srv.utils import RotatingLog from calibre.ptempfile import TemporaryDirectory with TemporaryDirectory() as tdir: fname = os.path.join(tdir, 'log') l = RotatingLog(fname, max_size=100) def history(): return {int(x.rpartition('.')[-1]) for x in glob(fname + '.*')} def log_size(): ssize = l.outputs[0].stream.tell() self.ae(ssize, os.path.getsize(fname)) return ssize self.ae(log_size(), 0) l('a' * 99) self.ae(log_size(), 100) l('b'), l('c') self.ae(log_size(), 2) self.ae(history(), {1}) for i in 'abcdefg': l(i * 101) self.assertLessEqual(log_size(), 100) self.ae(history(), {1,2,3,4,5}) def test_plugins(self): 'Test plugin semantics' class Plugin: def __init__(self): self.running = Event() self.event = Event() self.port = None def start(self, loop): self.running.set() self.port = loop.bound_address[1] self.event.wait() self.running.clear() def stop(self): self.event.set() plugin = Plugin() with TestServer(lambda data:'xxx', plugins=(plugin,)) as server: self.assertTrue(plugin.running.wait(0.2)) self.ae(plugin.port, server.address[1]) self.assertTrue(plugin.event.wait(5)) self.assertFalse(plugin.running.is_set()) def test_workers(self): ' Test worker semantics ' with TestServer(lambda data:(data.path[0] + data.read()), worker_count=3) as server: self.ae(3, sum(int(w.is_alive()) for w in server.loop.pool.workers)) self.ae(0, sum(int(w.is_alive()) for w in server.loop.pool.workers)) # Test shutdown with hung worker block = Event() with TestServer(lambda data:block.wait(), worker_count=3, shutdown_timeout=10, timeout=10) as server: pool = server.loop.pool self.ae(3, sum(int(w.is_alive()) for w in pool.workers)) conn = server.connect() conn.request('GET', '/') with self.assertRaises(socket.timeout): res = conn.getresponse() if int(res.status) == int(http_client.REQUEST_TIMEOUT): raise socket.timeout('Timeout') raise Exception('Got unexpected response: code: {} {} headers: {!r} data: {!r}'.format( res.status, res.reason, res.getheaders(), res.read())) self.ae(pool.busy, 1) self.ae(1, sum(int(w.is_alive()) for w in pool.workers)) block.set() for w in pool.workers: w.join() self.ae(0, sum(int(w.is_alive()) for w in server.loop.pool.workers)) def test_fallback_interface(self): 'Test falling back to default interface' with TestServer(lambda data:(data.path[0] + data.read()), listen_on='1.1.1.1', fallback_to_detected_interface=True) as server: self.assertNotEqual('1.1.1.1', server.address[0]) @skipIf(True, 'Disabled as it is failing on the build server, need to investigate') def test_bonjour(self): 'Test advertising via BonJour' from calibre.srv.bonjour import BonJour from zeroconf import Zeroconf b = BonJour(wait_for_stop=False) with TestServer(lambda data:(data.path[0] + data.read()), plugins=(b,), shutdown_timeout=500) as server: self.assertTrue(b.started.wait(50), 'BonJour not started') self.ae(b.advertised_port, server.address[1]) service = b.services[0] self.ae(service.type, '_calibre._tcp.local.') r = Zeroconf() info = r.get_service_info(service.type, service.name) self.assertIsNotNone(info) self.ae(info.text, b'\npath=/opds') self.assertTrue(b.stopped.wait(5), 'BonJour not stopped') def test_dual_stack(self): from calibre.srv.loop import IPPROTO_IPV6 with TestServer(lambda data:(data.path[0] + data.read().decode('utf-8')), listen_on='::') as server: self.ae(server.address[0], '::') self.ae(server.loop.socket.getsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY), 0) conn = server.connect(interface='127.0.0.1') conn.request('GET', '/test', 'body') r = conn.getresponse() self.ae(r.status, http_client.OK) self.ae(r.read(), b'testbody') def test_ring_buffer(self): 'Test the ring buffer used for reads' class FakeSocket: def __init__(self, data): self.data = data def recv_into(self, mv): sz = min(len(mv), len(self.data)) mv[:sz] = self.data[:sz] return sz from calibre.srv.loop import ReadBuffer, READ, WRITE buf = ReadBuffer(100) def write(data): return buf.recv_from(FakeSocket(data)) def set(data, rpos, wpos, state): buf.ba = bytearray(data) buf.buf = memoryview(buf.ba) buf.read_pos, buf.write_pos, buf.full_state = rpos, wpos, state self.ae(b'', buf.read(10)) self.assertTrue(buf.has_space), self.assertFalse(buf.has_data) self.ae(write(b'a'*50), 50) self.ae(write(b'a'*50), 50) self.ae(write(b'a'*50), 0) self.ae(buf.read(1000), bytes(buf.ba)) self.ae(b'', buf.read(10)) self.ae(write(b'a'*10), 10) numbers = bytes(bytearray(range(10))) set(numbers, 1, 3, READ) self.ae(buf.read(1), b'\x01') self.ae(buf.read(10), b'\x02') self.ae(buf.full_state, WRITE) set(numbers, 3, 1, READ) self.ae(buf.read(1), b'\x03') self.ae(buf.read(10), b'\x04\x05\x06\x07\x08\x09\x00') set(numbers, 1, 3, READ) self.ae(buf.readline(), b'\x01\x02') set(b'123\n', 0, 3, READ) self.ae(buf.readline(), b'123') set(b'123\n', 0, 0, READ) self.ae(buf.readline(), b'123\n') self.ae(buf.full_state, WRITE) set(b'1\n2345', 2, 2, READ) self.ae(buf.readline(), b'23451\n') self.ae(buf.full_state, WRITE) set(b'1\n2345', 1, 1, READ) self.ae(buf.readline(), b'\n') set(b'1\n2345', 4, 1, READ) self.ae(buf.readline(), b'451') set(b'1\n2345', 4, 2, READ) self.ae(buf.readline(), b'451\n') set(b'123456\n7', 4, 2, READ) self.ae(buf.readline(), b'56\n') def test_ssl(self): 'Test serving over SSL' address = '127.0.0.1' with TemporaryDirectory('srv-test-ssl') as tdir: cert_file, key_file, ca_file = map(lambda x:os.path.join(tdir, x), 'cka') create_server_cert(address, ca_file, cert_file, key_file, key_size=2048) ctx = ssl.create_default_context(cafile=ca_file) with TestServer( lambda data:(data.path[0] + data.read().decode('utf-8')), ssl_certfile=cert_file, ssl_keyfile=key_file, listen_on=address, port=0) as server: conn = http_client.HTTPSConnection(address, server.address[1], context=ctx) conn.request('GET', '/test', 'body') r = conn.getresponse() self.ae(r.status, http_client.OK) self.ae(r.read(), b'testbody') cert = conn.sock.getpeercert() subject = dict(x[0] for x in cert['subject']) self.ae(subject['commonName'], address) @skipIf(not has_preactivated_support, 'pre_activated_socket not available') def test_socket_activation(self): 'Test socket activation' os.closerange(3, 4) # Ensure the socket gets fileno == 3 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) s.bind(('localhost', 0)) port = s.getsockname()[1] self.ae(s.fileno(), 3) os.environ['LISTEN_PID'] = str(os.getpid()) os.environ['LISTEN_FDS'] = '1' with TestServer(lambda data:(data.path[0].encode('utf-8') + data.read()), allow_socket_preallocation=True) as server: conn = server.connect() conn.request('GET', '/test', 'body') r = conn.getresponse() self.ae(r.status, http_client.OK) self.ae(r.read(), b'testbody') self.ae(server.loop.bound_address[1], port) def test_monotonic(self): 'Test the monotonic() clock' a = monotonic() b = monotonic() self.assertGreaterEqual(b, a) a = monotonic() time.sleep(0.1) b = monotonic() self.assertGreaterEqual(b, a) self.assertGreaterEqual(b - a, 0.09) self.assertLessEqual(b - a, 0.4) def test_jobs_manager(self): 'Test the jobs manager' from calibre.srv.jobs import JobsManager O = namedtuple('O', 'max_jobs max_job_time') class FakeLog(list): def error(self, *args): self.append(' '.join(args)) s = ('waiting', 'running') jm = JobsManager(O(1, 5), FakeLog()) def job_status(jid): return jm.job_status(jid)[0] # Start jobs job_id1 = jm.start_job('simple test', 'calibre.srv.jobs', 'sleep_test', args=(1.0,)) job_id2 = jm.start_job('t2', 'calibre.srv.jobs', 'sleep_test', args=(3,)) job_id3 = jm.start_job('err test', 'calibre.srv.jobs', 'error_test') # Job 1 job_id = job_id1 status = jm.job_status(job_id)[0] self.assertIn(status, s) for jid in (job_id2, job_id3): self.assertEqual(job_status(jid), 'waiting') while job_status(job_id) in s: time.sleep(0.01) status, result, tb, was_aborted = jm.job_status(job_id) self.assertEqual(status, 'finished') self.assertFalse(was_aborted) self.assertFalse(tb) self.assertEqual(result, 1.0) # Job 2 job_id = job_id2 while job_status(job_id) == 'waiting': time.sleep(0.01) self.assertEqual('running', job_status(job_id)) jm.abort_job(job_id) self.assertIn(jm.wait_for_running_job(job_id), (True, None)) status, result, tb, was_aborted = jm.job_status(job_id) self.assertEqual('finished', status) self.assertTrue(was_aborted) # Job 3 job_id = job_id3 while job_status(job_id) == 'waiting': time.sleep(0.01) self.assertIn(jm.wait_for_running_job(job_id), (True, None)) status, result, tb, was_aborted = jm.job_status(job_id) self.assertEqual(status, 'finished') self.assertFalse(was_aborted) self.assertTrue(tb) self.assertIn('a testing error', tb) jm.start_job('simple test', 'calibre.srv.jobs', 'sleep_test', args=(1.0,)) jm.shutdown(), jm.wait_for_shutdown(monotonic() + 1) def find_tests(): import unittest return unittest.defaultTestLoader.loadTestsFromTestCase(LoopTest)