24.09., 9:00 - 11:00: Due to updates GitLab will be unavailable for some minutes between 09:00 and 11:00.

test_server.py 8.07 KB
Newer Older
di68kap's avatar
di68kap committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#!/usr/bin/python3

"""test_server.py - tests of the server module of DHParser


Author: Eckhart Arnold <arnold@badw.de>

Copyright 2019 Bavarian Academy of Sciences and Humanities

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import asyncio
24
import json
di68kap's avatar
di68kap committed
25
import os
26
import subprocess
di68kap's avatar
di68kap committed
27
import sys
28
import time
di68kap's avatar
di68kap committed
29

eckhart's avatar
eckhart committed
30 31
sys.path.extend(['../', './'])

Eckhart Arnold's avatar
Eckhart Arnold committed
32
from DHParser.server import Server, STOP_SERVER_REQUEST, IDENTIFY_REQUEST, SERVER_OFFLINE, asyncio_run
33
from DHParser.toolkit import concurrent_ident
di68kap's avatar
di68kap committed
34 35 36 37 38


scriptdir = os.path.dirname(os.path.realpath(__file__))


39 40
# def compiler_dummy(src: str, log_dir: str='') -> Tuple[str, str]:
#     return src
di68kap's avatar
di68kap committed
41

eckhart's avatar
eckhart committed
42

di68kap's avatar
di68kap committed
43
class TestServer:
44 45 46 47
    # def test_server(self):
    #     cs = Server(compiler_dummy)
    #     cs.run_server()

48 49 50
    def setup(self):
        self.windows = sys.platform.lower().find('win') >= 0

51
    def compiler_dummy(self, src: str) -> str:
52
        return src
eckhart's avatar
eckhart committed
53

54 55 56 57
    def long_running(self, duration: str) -> str:
        time.sleep(float(duration))
        return(duration)

58 59
    def test_server_proces(self):
        """Basic Test of server module."""
60
        async def compile_remote(src):
61
            reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
di68kap's avatar
di68kap committed
62
            writer.write(src.encode())
Eckhart Arnold's avatar
Eckhart Arnold committed
63
            data = await reader.read(500)
di68kap's avatar
di68kap committed
64
            writer.close()
65
            assert data.decode() == "Test", data.decode()
di68kap's avatar
di68kap committed
66
        cs = Server(self.compiler_dummy, cpu_bound=set())
67 68
        try:
            cs.spawn_server('127.0.0.1', 8888)
69
            asyncio_run(compile_remote('Test'))
70
        finally:
71
            cs.terminate_server()
72

Eckhart Arnold's avatar
Eckhart Arnold committed
73 74 75 76 77 78 79 80 81 82 83 84 85
    def test_indentify(self):
        """Test server's 'identify/'-command."""
        async def send_request(request):
            reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
            writer.write(request.encode() if isinstance(request, str) else request)
            data = await reader.read(500)
            writer.close()
            return data.decode()

        cs = Server(self.compiler_dummy)
        try:
            cs.spawn_server('127.0.0.1', 8888)
            result = asyncio_run(send_request(IDENTIFY_REQUEST))
86
            assert result.startswith('DHParser'), result
Eckhart Arnold's avatar
Eckhart Arnold committed
87 88 89
        finally:
            cs.terminate_server()

90
    def test_terminate(self):
91 92
        """Test different ways of sending a termination message to server:
        http-request, plain-text and json-rpc."""
93 94 95 96 97 98 99 100
        async def terminate_server(termination_request, expected_response):
            reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
            writer.write(termination_request)
            data = await reader.read(500)
            writer.close()
            # print(data)
            assert data.find(expected_response) >= 0, str(data)

di68kap's avatar
di68kap committed
101
        cs = Server(self.compiler_dummy, cpu_bound=set())
102 103
        try:
            cs.spawn_server('127.0.0.1', 8888)
104
            asyncio_run(terminate_server(STOP_SERVER_REQUEST,
105 106 107 108 109
                                         b'DHParser server at 127.0.0.1:8888 stopped!'))
            cs.wait_for_termination()
            assert cs.stage.value == SERVER_OFFLINE

            cs.spawn_server('127.0.0.1', 8888)
110
            asyncio_run(terminate_server(b'GET ' + STOP_SERVER_REQUEST + b' HTTP',
111 112 113 114 115
                                         b'DHParser server at 127.0.0.1:8888 stopped!'))
            cs.wait_for_termination()
            assert cs.stage.value == SERVER_OFFLINE

            cs.spawn_server('127.0.0.1', 8888)
116 117
            jsonrpc = json.dumps({"jsonrpc": "2.0", "method": STOP_SERVER_REQUEST.decode(),
                                  'id': 1})
118
            asyncio_run(terminate_server(jsonrpc.encode(),
119 120 121 122
                                         b'DHParser server at 127.0.0.1:8888 stopped!'))
            cs.wait_for_termination()
            assert cs.stage.value == SERVER_OFFLINE
        finally:
123 124 125 126 127 128
            cs.terminate_server()

    def test_long_running_task(self):
        """Test, whether delegation of (long-running) tasks to
        processes or threads works."""
        sequence = []
129 130 131 132
        if self.windows:
            SLOW, FAST = '0.1', '0.01'
        else:
            SLOW, FAST = '0.01', '0.001'
133 134 135 136 137 138 139 140 141

        async def call_remote(argument):
            sequence.append(argument)
            reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
            writer.write(argument.encode())
            sequence.append((await reader.read(500)).decode())
            writer.close()

        async def run_tasks():
142 143
            await asyncio.gather(call_remote(SLOW),
                                 call_remote(FAST))
144

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
        if sys.version_info >= (3, 6):
            cs = Server(self.long_running,
                        cpu_bound=frozenset(['long_running']),
                        blocking=frozenset())
            try:
                cs.spawn_server('127.0.0.1', 8888)
                asyncio_run(run_tasks())
                assert sequence == [SLOW, FAST, FAST, SLOW], str(sequence)
            finally:
                cs.terminate_server()

            cs = Server(self.long_running,
                        cpu_bound=frozenset(),
                        blocking=frozenset(['long_running']))
            try:
                sequence = []
                cs.spawn_server('127.0.0.1', 8888)
                asyncio_run(run_tasks())
                assert sequence == [SLOW, FAST, FAST, SLOW]
            finally:
                cs.terminate_server()
166 167 168 169 170 171 172 173

        cs = Server(self.long_running,
                    cpu_bound=frozenset(),
                    blocking=frozenset())
        try:
            sequence = []
            cs.spawn_server('127.0.0.1', 8888)
            asyncio_run(run_tasks())
174 175
            # if run asyncronously, order os results is arbitrary
            assert sequence.count(SLOW) == 2 and sequence.count(FAST) == 2
176 177
        finally:
            cs.terminate_server()
178

di68kap's avatar
di68kap committed
179

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
RUN_SERVER_SCRIPT = """
def dummy(s: str) -> str:
    return s

def run_server(host, port):
    from DHParser.server import Server
    print('Starting server on %s:%i' % (host, port))
    server = Server(dummy)
    server.run_server(host, port)

if __name__ == '__main__':
    run_server('127.0.0.1', 8888)
"""

def asyncio_run(coroutine):
    """Backward compatible version of Pyhon 3.7's `asyncio.run()`"""
    if sys.version_info >= (3, 7):
        return asyncio.run(coroutine)
    else:
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(coroutine)

class TestSpawning:
    """Tests spawning a server by starting a script via subprocess.Popen."""

    def setup(self):
        self.tmpdir = 'tmp_' + concurrent_ident()
        os.mkdir(self.tmpdir)

    def teardown(self):
        for fname in os.listdir(self.tmpdir):
            os.remove(os.path.join(self.tmpdir, fname))
        os.rmdir(self.tmpdir)

    def test_spawn(self):
        scriptname = os.path.join(self.tmpdir, 'spawn_server.py')
        with open(scriptname, 'w') as f:
            f.write(RUN_SERVER_SCRIPT)
        subprocess.Popen(['python', scriptname])
        countdown = 20
        delay = 0.05
        connected = False
        reader, writer = None, None
        while countdown > 0:
            try:
                reader, writer = asyncio_run(asyncio.open_connection('127.0.0.1', 8888))
                countdown = 0
                connected = True
            except ConnectionRefusedError:
                time.sleep(delay)
                delay += 0.0
                countdown -= 1
        if connected:
            writer.write(IDENTIFY_REQUEST.encode())
            data = asyncio_run(reader.read(500))
            writer.close()
        print(data.decode())



di68kap's avatar
di68kap committed
240 241 242
if __name__ == "__main__":
    from DHParser.testing import runner
    runner("", globals())