...
 
Commits (3)
......@@ -761,7 +761,7 @@ class EBNFCompiler(Compiler):
task()
# provide for capturing of symbols that are variables, i.e. the
# value of will be retrieved at some point during the parsing process
# value of which will be retrieved at some point during the parsing process
if self.variables:
for i in range(len(definitions)):
......
......@@ -46,7 +46,7 @@ from concurrent.futures import Executor, ThreadPoolExecutor, ProcessPoolExecutor
from concurrent.futures.process import BrokenProcessPool
from functools import partial
import json
from multiprocessing import Value, Array
from multiprocessing import Process, Value, Array
import platform
import os
import subprocess
......@@ -886,6 +886,35 @@ class Server:
self.stage.value = SERVER_OFFLINE
def run_server(host, port, rpc_functions: RPC_Type,
cpu_bound: Set[str] = ALL_RPCs,
blocking: Set[str] = frozenset()):
"""Start a server and wait until server is closed."""
server = Server(rpc_functions, cpu_bound, blocking)
server.run_server(host, port)
def dummy_server(s: str) -> str:
return s
def spawn_server(host: str = USE_DEFAULT_HOST,
port: int = USE_DEFAULT_PORT,
parameters: Union[Tuple, Callable] = dummy_server) -> Process:
"""
Start DHParser-Server in a separate process and return.
Useful for writing test code.
WARNING: Does not seem to work with multiprocessing.set_start_method('spawn')
under linux !?
"""
if isinstance(parameters, tuple) or isinstance(parameters, list):
p = Process(target=run_server, args=(host, port, *parameters))
else:
p = Process(target=run_server, args=(host, port, parameters))
p.start()
return p
RUN_SERVER_SCRIPT_TEMPLATE = """
import os
import sys
......@@ -903,7 +932,7 @@ def run_server(host, port):
from DHParser.server import asyncio_run, Server, stop_server, has_server_stopped
{LOGGING}
stop_server(host, port, 2.0)
server = Server({PARAMETERS})
server = Server({PARAMETERS})
server.run_server(host, port)
if __name__ == '__main__':
......@@ -919,14 +948,14 @@ LOGGING_BLOCK = """from DHParser.log import start_logging
python_interpreter_name_cached = ''
def spawn_server(host: str = USE_DEFAULT_HOST,
def detach_server(host: str = USE_DEFAULT_HOST,
port: int = USE_DEFAULT_PORT,
initialization: str = '',
parameters: str = 'lambda s: s',
import_path: str = '.'):
"""
Start DHParser-Server in a separate process and return.
Useful for writing test code.
Start DHParser-Server in a separate process and return. The process remains
active even after the parent process is closed. Useful for writing test code.
"""
async def wait_for_connection(host, port):
reader, writer = await asyncio_connect(host, port) # wait until server online
......@@ -952,7 +981,10 @@ def spawn_server(host: str = USE_DEFAULT_HOST,
HOST=host, PORT=port, INITIALIZATION=initialization, LOGGING=logging,
PARAMETERS=parameters, IMPORT_PATH=import_path.replace('\\', '\\\\'))
# print(run_server_script)
subprocess.Popen([interpreter, '-c', run_server_script], encoding="utf-8")
if sys.version_info >= (3, 6):
subprocess.Popen([interpreter, '-c', run_server_script], encoding="utf-8")
else:
subprocess.Popen([interpreter, '-c', run_server_script])
asyncio_run(wait_for_connection(host, port))
......
......@@ -184,7 +184,8 @@ def run_server(host, port):
DSL_server = Server(rpc_functions=lsp_table,
cpu_bound=set(lsp_table.keys() - non_blocking),
blocking=frozenset())
DSL_server.run_server(host, port)
DSL_server.run_server(host, port) # returns only after server has stopped
cfg_filename = get_config_filename()
try:
......
......@@ -22,11 +22,6 @@ limitations under the License.
# TODO: Quite slow under MS Windows
if __name__ == "__main__":
import multiprocessing
multiprocessing.freeze_support()
multiprocessing.set_start_method('spawn') # 'spawn' (windows and linux)
# or 'fork' or 'forkserver' or 'spawn' (linux only)
import asyncio
import functools
......@@ -37,6 +32,13 @@ import sys
import time
from typing import Callable
if __name__ == "__main__" and sys.platform.lower().startswith('win'):
# import multiprocessing
multiprocessing.freeze_support()
multiprocessing.set_start_method('spawn') # 'spawn' (windows and linux)
# or 'fork' or 'forkserver' (linux only)
scriptpath = os.path.abspath(os.path.dirname(__file__) or '.')
sys.path.append(os.path.abspath(os.path.join(scriptpath, '..')))
......@@ -105,14 +107,15 @@ class TestServer:
writer.close()
if sys.version_info >= (3, 7): await writer.wait_closed()
assert data.decode() == "Test", data.decode()
p = None
try:
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import compiler_dummy',
'compiler_dummy, cpu_bound=set()',
import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT,
(compiler_dummy, set()))
asyncio_run(compile_remote('Test'))
finally:
stop_server('127.0.0.1', TEST_PORT)
if p is not None:
p.join()
def test_identify(self):
"""Test server's 'identify/'-command."""
......@@ -124,17 +127,16 @@ class TestServer:
writer.close()
if sys.version_info >= (3, 7): await writer.wait_closed()
return data.decode()
p = None
try:
from timeit import timeit
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import compiler_dummy',
'compiler_dummy',
import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT, compiler_dummy)
result = asyncio_run(send_request(IDENTIFY_REQUEST))
assert isinstance(result, str) and result.startswith('DHParser'), result
finally:
stop_server('127.0.0.1', TEST_PORT)
if p is not None:
p.join()
def test_terminate(self):
"""Test different ways of sending a termination message to server:
......@@ -147,28 +149,22 @@ class TestServer:
if sys.version_info >= (3, 7): await writer.wait_closed()
# print(data)
assert data.find(expected_response) >= 0, str(data)
p = None
try:
# plain text stop request
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import compiler_dummy',
'compiler_dummy, cpu_bound=set()', import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT, (compiler_dummy, set()))
asyncio_run(terminate_server(STOP_SERVER_REQUEST,
b'DHParser server at 127.0.0.1:%i stopped!' % TEST_PORT))
assert asyncio_run(has_server_stopped('127.0.0.1', TEST_PORT))
# http stop request
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import compiler_dummy',
'compiler_dummy, cpu_bound=set()', import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT, (compiler_dummy, set()))
asyncio_run(terminate_server(b'GET ' + STOP_SERVER_REQUEST + b' HTTP',
b'DHParser server at 127.0.0.1:%i stopped!' % TEST_PORT))
assert asyncio_run(has_server_stopped('127.0.0.1', TEST_PORT))
# json_rpc stop request
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import compiler_dummy',
'compiler_dummy, cpu_bound=set()', import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT, (compiler_dummy, set()))
jsonrpc = json.dumps({"jsonrpc": "2.0", "method": STOP_SERVER_REQUEST.decode(),
'id': 1})
asyncio_run(terminate_server(jsonrpc.encode(),
......@@ -176,6 +172,8 @@ class TestServer:
assert asyncio_run(has_server_stopped('127.0.0.1', TEST_PORT))
finally:
stop_server('127.0.0.1', TEST_PORT)
if p is not None:
p.join()
def test_long_running_task(self):
"""Test, whether delegation of (long-running) tasks to
......@@ -199,37 +197,38 @@ class TestServer:
call_remote(FAST))
if sys.version_info >= (3, 6):
p = None
try:
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import long_running',
"long_running, cpu_bound=frozenset(['long_running']), "
"blocking=frozenset()", import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT,
(long_running, frozenset(['long_running']), frozenset()))
asyncio_run(run_tasks())
assert sequence == [SLOW, FAST, FAST, SLOW], str(sequence)
finally:
stop_server('127.0.0.1', TEST_PORT)
if p is not None:
p.join()
sequence = []
p = None
try:
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import long_running',
"long_running, cpu_bound=frozenset(), "
"blocking=frozenset(['long_running'])", import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT,
(long_running, frozenset(), frozenset(['long_running'])))
asyncio_run(run_tasks())
assert sequence == [SLOW, FAST, FAST, SLOW], str(sequence)
finally:
stop_server('127.0.0.1', TEST_PORT)
if p is not None:
p.join()
sequence = []
p = None
try:
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import long_running',
"long_running, cpu_bound=frozenset(), blocking=frozenset()",
import_path=scriptpath)
p = spawn_server('127.0.0.1', TEST_PORT,
(long_running, frozenset(), frozenset()))
asyncio_run(run_tasks())
assert sequence.count(SLOW) == 2 and sequence.count(FAST) == 2
finally:
stop_server('127.0.0.1', TEST_PORT)
if p is not None:
p.join()
sequence = []
......@@ -243,8 +242,7 @@ class TestSpawning:
stop_server('127.0.0.1', TEST_PORT)
def test_spawn(self):
spawn_server('127.0.0.1', TEST_PORT, import_path=scriptpath)
spawn_server('127.0.0.1', TEST_PORT)
async def identify():
try:
reader, writer = await asyncio_connect('127.0.0.1', TEST_PORT)
......@@ -256,7 +254,6 @@ class TestSpawning:
return data.decode()
except ConnectionRefusedError:
return ''
result = asyncio_run(identify())
assert result.startswith('DHParser'), result
......@@ -333,6 +330,7 @@ class TestLanguageServer:
def setup(self):
stop_server('127.0.0.1', TEST_PORT)
self.p = None
self.DEBUG = False
if self.DEBUG:
from DHParser import log
......@@ -341,18 +339,19 @@ class TestLanguageServer:
def teardown(self):
stop_server('127.0.0.1', TEST_PORT)
if self.p is not None:
self.p.join()
if self.DEBUG:
from DHParser import log
log.suspend_logging()
def start_server(self):
stop_server('127.0.0.1', TEST_PORT)
spawn_server('127.0.0.1', TEST_PORT,
'from test_server import LSP, gen_lsp_table\n'
'lsp = LSP()\n'
"lsp_table = gen_lsp_table(LSP(), prefix='lsp_')\n",
"lsp_table, cpu_bound=frozenset(), "
"blocking=frozenset()", import_path=scriptpath)
if self.p is not None:
self.p.join()
self.lsp = LSP()
lsp_table = gen_lsp_table(self.lsp, prefix='lsp_')
self.p = spawn_server('127.0.0.1', TEST_PORT, (lsp_table, frozenset(), frozenset()))
def test_initialize(self):
self.start_server()
......
This diff is collapsed.