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: ...@@ -44,8 +44,8 @@ For JSON see:
# TODO: Test with python 3.5 # TODO: Test with python 3.5
import asyncio import asyncio
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, CancelledError, \ from concurrent.futures import Executor, ThreadPoolExecutor, ProcessPoolExecutor, CancelledError
BrokenExecutor from concurrent.futures.process import BrokenProcessPool
import json import json
from multiprocessing import Process, Queue, Value, Array from multiprocessing import Process, Queue, Value, Array
import sys import sys
...@@ -233,20 +233,13 @@ class Server: ...@@ -233,20 +233,13 @@ class Server:
response = RESPONSE_HEADER.format(date=gmt, length=len(encoded_html)) response = RESPONSE_HEADER.format(date=gmt, length=len(encoded_html))
return response.encode() + 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 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: 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: if executor is None:
result = method(**params) if has_kw_params else method(*params) result = method(**params) if has_kw_params else method(*params)
elif has_kw_params: elif has_kw_params:
...@@ -257,11 +250,26 @@ class Server: ...@@ -257,11 +250,26 @@ class Server:
rpc_error = -32602, "Invalid Params: " + str(e) rpc_error = -32602, "Invalid Params: " + str(e)
except NameError as e: except NameError as e:
rpc_error = -32601, "Method not found: " + str(e) rpc_error = -32601, "Method not found: " + str(e)
except BrokenExecutor as e: except BrokenProcessPool as e:
rpc_error = -32000, "Broken Executor: " + str(e) rpc_error = -32050, "Broken Executor: " + str(e)
except Exception as e: except Exception as e:
rpc_error = -32000, "Server Error " + str(type(e)) + ': ' + str(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'): if data.startswith(b'GET'):
# HTTP request # HTTP request
m = re.match(RE_GREP_URL, data) m = re.match(RE_GREP_URL, data)
...@@ -379,9 +387,12 @@ class Server: ...@@ -379,9 +387,12 @@ class Server:
if port == USE_DEFAULT_PORT: if port == USE_DEFAULT_PORT:
port = get_config_value('server_default_port') port = get_config_value('server_default_port')
assert port >= 0 assert port >= 0
with ProcessPoolExecutor() as p, ThreadPoolExecutor() as t: # with ProcessPoolExecutor() as p, ThreadPoolExecutor() as t:
self.pp_executor = p try:
self.tp_executor = t 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.stop_response = "DHParser server at {}:{} stopped!".format(host, port)
self.host.value = host.encode() self.host.value = host.encode()
self.port.value = port self.port.value = port
...@@ -391,6 +402,13 @@ class Server: ...@@ -391,6 +402,13 @@ class Server:
self.stage.value = SERVER_ONLINE self.stage.value = SERVER_ONLINE
self.server_messages.put(SERVER_ONLINE) self.server_messages.put(SERVER_ONLINE)
await self.server.serve_forever() 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): def serve_py35(self, host: str = USE_DEFAULT_HOST, port: int = USE_DEFAULT_PORT, loop=None):
if host == USE_DEFAULT_HOST: if host == USE_DEFAULT_HOST:
......
...@@ -42,7 +42,7 @@ def run_grammar_tests(glob_pattern, get_grammar, get_transformer): ...@@ -42,7 +42,7 @@ def run_grammar_tests(glob_pattern, get_grammar, get_transformer):
error_report = testing.grammar_suite( error_report = testing.grammar_suite(
os.path.join(scriptpath, 'grammar_tests'), os.path.join(scriptpath, 'grammar_tests'),
get_grammar, get_transformer, get_grammar, get_transformer,
fn_patterns=[glob_pattern], report=True, verbose=True) fn_patterns=[glob_pattern], report='REPORT', verbose=True)
return error_report return error_report
......
...@@ -289,7 +289,7 @@ def get_report(test_unit): ...@@ -289,7 +289,7 @@ def get_report(test_unit):
return '\n'.join(report) 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. 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 ...@@ -491,14 +491,13 @@ def grammar_unit(test_unit, parser_factory, transformer_factory, report=True, ve
# write test-report # write test-report
if report: if report:
report_dir = "REPORT"
test_report = get_report(test_unit) test_report = get_report(test_unit)
if test_report: if test_report:
try: try:
os.mkdir(report_dir) os.mkdir(report) # is a process-Lock needed, here?
except FileExistsError: except FileExistsError:
pass 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) f.write(test_report)
print('\n'.join(output)) print('\n'.join(output))
...@@ -532,7 +531,7 @@ def run_unit(logdir, *parameters): ...@@ -532,7 +531,7 @@ def run_unit(logdir, *parameters):
def grammar_suite(directory, parser_factory, transformer_factory, def grammar_suite(directory, parser_factory, transformer_factory,
fn_patterns=['*test*'], fn_patterns=['*test*'],
ignore_unknown_filetypes=False, 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 Runs all grammar unit tests in a directory. A file is considered a test
unit, if it has the word "test" in its name. unit, if it has the word "test" in its name.
...@@ -850,17 +849,17 @@ def run_path(path): ...@@ -850,17 +849,17 @@ def run_path(path):
sys.path.pop() sys.path.pop()
def clean_report(): def clean_report(report_dir='REPORT'):
"""Deletes any test-report-files in the REPORT sub-directory and removes """Deletes any test-report-files in the REPORT sub-directory and removes
the REPORT sub-directory, if it is empty after deleting the files.""" the REPORT sub-directory, if it is empty after deleting the files."""
# TODO: make this thread safe, if possible!!!! # TODO: make this thread safe, if possible!!!!
if os.path.exists('REPORT'): if os.path.exists(report_dir):
files = os.listdir('REPORT') files = os.listdir(report_dir)
flag = False flag = False
for file in files: for file in files:
if re.match(r'\w*_test_\d+\.md', file): 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: else:
flag = True flag = True
if not flag: if not flag:
os.rmdir('REPORT') os.rmdir(report_dir)
...@@ -138,7 +138,7 @@ class TestEBNFParser: ...@@ -138,7 +138,7 @@ class TestEBNFParser:
self.EBNF = get_ebnf_grammar() self.EBNF = get_ebnf_grammar()
def teardown(self): def teardown(self):
clean_report() clean_report('REPORT_TestEBNFParser')
os.chdir(self.save_dir) os.chdir(self.save_dir)
def test_RE(self): def test_RE(self):
...@@ -166,7 +166,7 @@ class TestEBNFParser: ...@@ -166,7 +166,7 @@ class TestEBNFParser:
assert not result.error_flag assert not result.error_flag
def test_list(self): 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: ...@@ -207,18 +207,18 @@ class TestGrammarTest:
os.chdir(scriptdir) os.chdir(scriptdir)
def teardown(self): def teardown(self):
clean_report() clean_report('REPORT_TestGrammarTest')
os.chdir(self.save_dir) os.chdir(self.save_dir)
def test_testing_grammar(self): def test_testing_grammar(self):
parser_fac = grammar_provider(ARITHMETIC_EBNF) parser_fac = grammar_provider(ARITHMETIC_EBNF)
trans_fac = lambda : ARITHMETIC_EBNFTransform trans_fac = lambda : ARITHMETIC_EBNFTransform
# reset_unit(self.cases) # 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!?" assert errata, "Unknown parser, but no error message!?"
report = get_report(self.cases) report = get_report(self.cases)
assert report.find('### CST') >= 0 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) assert len(errata) == 3, str(errata)
# def test_get_report(self): # def test_get_report(self):
...@@ -236,7 +236,8 @@ class TestGrammarTest: ...@@ -236,7 +236,8 @@ class TestGrammarTest:
fcases['berm']['fail'] = self.failure_cases['term']['fail'] fcases['berm']['fail'] = self.failure_cases['term']['fail']
errata = grammar_unit(fcases, errata = grammar_unit(fcases,
grammar_provider(ARITHMETIC_EBNF), grammar_provider(ARITHMETIC_EBNF),
lambda : ARITHMETIC_EBNFTransform) lambda : ARITHMETIC_EBNFTransform,
'REPORT_TestGrammarTest')
assert errata assert errata
...@@ -290,7 +291,7 @@ class TestLookahead: ...@@ -290,7 +291,7 @@ class TestLookahead:
self.trans_fac = lambda : partial(traverse, processing_table={"*": [flatten, remove_empty]}) self.trans_fac = lambda : partial(traverse, processing_table={"*": [flatten, remove_empty]})
def teardown(self): def teardown(self):
clean_report() clean_report('REPORT_TestLookahead')
os.chdir(self.save_dir) os.chdir(self.save_dir)
def test_selftest(self): def test_selftest(self):
...@@ -318,11 +319,13 @@ class TestLookahead: ...@@ -318,11 +319,13 @@ class TestLookahead:
# Case 2: Lookahead string is not part of the test case; parser matches but for the mandatory continuation # 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) result = gr(self.cases['category']['match'][2], 'category', True)
assert any(e.code == Error.MANDATORY_CONTINUATION_AT_EOF for e in result.errors) 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: for e in errata:
print (e) print (e)
assert not errata, str(errata) 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 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