Commit 9771686c authored by eckhart's avatar eckhart

- testing.py: report-dir is now passed as a parameter to grammar-unit. This...

- testing.py: report-dir is now passed as a parameter to grammar-unit. This helps avoiding conflicts when running tests in parallel
parent 63a54b5d
......@@ -44,8 +44,8 @@ For JSON see:
# TODO: Test with python 3.5
import asyncio
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, CancelledError, \
BrokenExecutor
from concurrent.futures import Executor, ThreadPoolExecutor, ProcessPoolExecutor, CancelledError
from concurrent.futures.process import BrokenProcessPool
import json
from multiprocessing import Process, Queue, Value, Array
import sys
......@@ -233,20 +233,13 @@ class Server:
response = RESPONSE_HEADER.format(date=gmt, length=len(encoded_html))
return response.encode() + encoded_html
async def run(method_name: str, method: Callable, params: Union[Dict, Sequence]):
async def execute(executor: Executor, method: Callable, params: Union[Dict, Sequence]):
nonlocal result, rpc_error
has_kw_params = isinstance(params, Dict)
assert has_kw_params or isinstance(params, Sequence)
loop = asyncio.get_running_loop() if sys.version_info >= (3, 7) \
else asyncio.get_event_loop()
try:
# run method either a) directly if it is short running or
# b) in a thread pool if it contains blocking io or
# c) in a process pool if it is cpu bound
# see: https://docs.python.org/3/library/asyncio-eventloop.html
# #executing-code-in-thread-or-process-pools
has_kw_params = isinstance(params, Dict)
assert has_kw_params or isinstance(params, Sequence)
loop = asyncio.get_running_loop() if sys.version_info >= (3, 7) \
else asyncio.get_event_loop()
executor = self.pp_executor if method_name in self.cpu_bound else \
self.tp_executor if method_name in self.blocking else None
if executor is None:
result = method(**params) if has_kw_params else method(*params)
elif has_kw_params:
......@@ -257,11 +250,26 @@ class Server:
rpc_error = -32602, "Invalid Params: " + str(e)
except NameError as e:
rpc_error = -32601, "Method not found: " + str(e)
except BrokenExecutor as e:
rpc_error = -32000, "Broken Executor: " + str(e)
except BrokenProcessPool as e:
rpc_error = -32050, "Broken Executor: " + str(e)
except Exception as e:
rpc_error = -32000, "Server Error " + str(type(e)) + ': ' + str(e)
async def run(method_name: str, method: Callable, params: Union[Dict, Sequence]):
# run method either a) directly if it is short running or
# b) in a thread pool if it contains blocking io or
# c) in a process pool if it is cpu bound
# see: https://docs.python.org/3/library/asyncio-eventloop.html
# #executing-code-in-thread-or-process-pools
executor = self.pp_executor if method_name in self.cpu_bound else \
self.tp_executor if method_name in self.blocking else None
await execute(executor, method, params)
if rpc_error is not None and rpc_error[0] == -32050:
# if process pool is broken, try again:
self.pp_executor.shutdown(wait=True)
self.pp_executor = ProcessPoolExecutor()
await execute(self.pp_executor, method, params)
if data.startswith(b'GET'):
# HTTP request
m = re.match(RE_GREP_URL, data)
......@@ -379,9 +387,12 @@ class Server:
if port == USE_DEFAULT_PORT:
port = get_config_value('server_default_port')
assert port >= 0
with ProcessPoolExecutor() as p, ThreadPoolExecutor() as t:
self.pp_executor = p
self.tp_executor = t
# with ProcessPoolExecutor() as p, ThreadPoolExecutor() as t:
try:
if self.pp_executor is None:
self.pp_executor = ProcessPoolExecutor()
if self.tp_executor is None:
self.tp_executor = ThreadPoolExecutor()
self.stop_response = "DHParser server at {}:{} stopped!".format(host, port)
self.host.value = host.encode()
self.port.value = port
......@@ -391,6 +402,13 @@ class Server:
self.stage.value = SERVER_ONLINE
self.server_messages.put(SERVER_ONLINE)
await self.server.serve_forever()
finally:
if self.tp_executor is not None:
self.tp_executor.shutdown(wait=True)
self.tp_executor = None
if self.pp_executor is not None:
self.pp_executor.shutdown(wait=True)
self.pp_executor = None
def serve_py35(self, host: str = USE_DEFAULT_HOST, port: int = USE_DEFAULT_PORT, loop=None):
if host == USE_DEFAULT_HOST:
......
......@@ -42,7 +42,7 @@ def run_grammar_tests(glob_pattern, get_grammar, get_transformer):
error_report = testing.grammar_suite(
os.path.join(scriptpath, 'grammar_tests'),
get_grammar, get_transformer,
fn_patterns=[glob_pattern], report=True, verbose=True)
fn_patterns=[glob_pattern], report='REPORT', verbose=True)
return error_report
......
......@@ -289,7 +289,7 @@ def get_report(test_unit):
return '\n'.join(report)
def grammar_unit(test_unit, parser_factory, transformer_factory, report=True, verbose=False):
def grammar_unit(test_unit, parser_factory, transformer_factory, report='REPORT', verbose=False):
"""
Unit tests for a grammar-parser and ast transformations.
"""
......@@ -491,14 +491,13 @@ def grammar_unit(test_unit, parser_factory, transformer_factory, report=True, ve
# write test-report
if report:
report_dir = "REPORT"
test_report = get_report(test_unit)
if test_report:
try:
os.mkdir(report_dir)
os.mkdir(report) # is a process-Lock needed, here?
except FileExistsError:
pass
with open(os.path.join(report_dir, unit_name + '.md'), 'w', encoding='utf8') as f:
with open(os.path.join(report, unit_name + '.md'), 'w', encoding='utf8') as f:
f.write(test_report)
print('\n'.join(output))
......@@ -532,7 +531,7 @@ def run_unit(logdir, *parameters):
def grammar_suite(directory, parser_factory, transformer_factory,
fn_patterns=['*test*'],
ignore_unknown_filetypes=False,
report=True, verbose=True):
report='REPORT', verbose=True):
"""
Runs all grammar unit tests in a directory. A file is considered a test
unit, if it has the word "test" in its name.
......@@ -850,17 +849,17 @@ def run_path(path):
sys.path.pop()
def clean_report():
def clean_report(report_dir='REPORT'):
"""Deletes any test-report-files in the REPORT sub-directory and removes
the REPORT sub-directory, if it is empty after deleting the files."""
# TODO: make this thread safe, if possible!!!!
if os.path.exists('REPORT'):
files = os.listdir('REPORT')
if os.path.exists(report_dir):
files = os.listdir(report_dir)
flag = False
for file in files:
if re.match(r'\w*_test_\d+\.md', file):
os.remove(os.path.join('REPORT', file))
os.remove(os.path.join(report_dir, file))
else:
flag = True
if not flag:
os.rmdir('REPORT')
os.rmdir(report_dir)
......@@ -138,7 +138,7 @@ class TestEBNFParser:
self.EBNF = get_ebnf_grammar()
def teardown(self):
clean_report()
clean_report('REPORT_TestEBNFParser')
os.chdir(self.save_dir)
def test_RE(self):
......@@ -166,7 +166,7 @@ class TestEBNFParser:
assert not result.error_flag
def test_list(self):
grammar_unit(self.cases, get_ebnf_grammar, get_ebnf_transformer)
grammar_unit(self.cases, get_ebnf_grammar, get_ebnf_transformer, 'REPORT_TestEBNFParser')
......
......@@ -207,18 +207,18 @@ class TestGrammarTest:
os.chdir(scriptdir)
def teardown(self):
clean_report()
clean_report('REPORT_TestGrammarTest')
os.chdir(self.save_dir)
def test_testing_grammar(self):
parser_fac = grammar_provider(ARITHMETIC_EBNF)
trans_fac = lambda : ARITHMETIC_EBNFTransform
# reset_unit(self.cases)
errata = grammar_unit(self.cases, parser_fac, trans_fac)
errata = grammar_unit(self.cases, parser_fac, trans_fac, 'REPORT_TestGrammarTest')
assert errata, "Unknown parser, but no error message!?"
report = get_report(self.cases)
assert report.find('### CST') >= 0
errata = grammar_unit(self.failure_cases, parser_fac, trans_fac)
errata = grammar_unit(self.failure_cases, parser_fac, trans_fac, 'REPORT_TestGrammarTest')
assert len(errata) == 3, str(errata)
# def test_get_report(self):
......@@ -236,7 +236,8 @@ class TestGrammarTest:
fcases['berm']['fail'] = self.failure_cases['term']['fail']
errata = grammar_unit(fcases,
grammar_provider(ARITHMETIC_EBNF),
lambda : ARITHMETIC_EBNFTransform)
lambda : ARITHMETIC_EBNFTransform,
'REPORT_TestGrammarTest')
assert errata
......@@ -290,7 +291,7 @@ class TestLookahead:
self.trans_fac = lambda : partial(traverse, processing_table={"*": [flatten, remove_empty]})
def teardown(self):
clean_report()
clean_report('REPORT_TestLookahead')
os.chdir(self.save_dir)
def test_selftest(self):
......@@ -318,11 +319,13 @@ class TestLookahead:
# Case 2: Lookahead string is not part of the test case; parser matches but for the mandatory continuation
result = gr(self.cases['category']['match'][2], 'category', True)
assert any(e.code == Error.MANDATORY_CONTINUATION_AT_EOF for e in result.errors)
errata = grammar_unit(self.cases, self.grammar_fac, self.trans_fac)
errata = grammar_unit(self.cases, self.grammar_fac, self.trans_fac,
'REPORT_TestLookahead')
for e in errata:
print (e)
assert not errata, str(errata)
errata = grammar_unit(self.fail_cases, self.grammar_fac, self.trans_fac)
errata = grammar_unit(self.fail_cases, self.grammar_fac, self.trans_fac,
'REPORT_TestLookahead')
assert errata
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment