Skip to content

Commit

Permalink
Merge pull request #20 from gleb-sevruk/release_1.2
Browse files Browse the repository at this point in the history
Release 1.2
  • Loading branch information
gleb-britecore authored Aug 18, 2020
2 parents 93abdc8 + 8c1e00a commit a3986e3
Show file tree
Hide file tree
Showing 21 changed files with 203 additions and 92 deletions.
3 changes: 0 additions & 3 deletions pycrunch/api/shared.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from asyncio import shield
from pprint import pprint
from time import perf_counter

import socketio
from pycrunch.watcher.fs_watcher import FSWatcher
Expand All @@ -14,7 +12,6 @@
# log_ws_internals = True
log_ws_internals = False
sio = socketio.AsyncServer(async_mode=async_mode, cors_allowed_origins='*', logger=log_ws_internals, engineio_logger=log_ws_internals)
timestamp = perf_counter


class ExternalPipe:
Expand Down
23 changes: 15 additions & 8 deletions pycrunch/api/socket_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
from pycrunch.api.shared import pipe
from pycrunch.pipeline import execution_pipeline
from pycrunch.pipeline.download_file_task import DownloadFileTask
from pycrunch.pipeline.run_test_task import RunTestTask
from pycrunch.pipeline.run_test_task import RunTestTask, RemoteDebugParams
from pycrunch.runner.pipeline_dispatcher import dispather_thread
from pycrunch.session import config
from pycrunch.session.state import engine
from pycrunch.shared.models import all_tests
from . import shared
from ..watchdog.connection_watchdog import connection_watchdog
from ..watchdog.tasks import TerminateTestExecutionTask
from ..watchdog.watchdog import watchdog_dispather_thread
from ..watchdog.watchdog_pipeline import watchdog_pipeline
Expand All @@ -30,12 +31,12 @@ def handle_message(message):


@shared.sio.on('json')
def handle_json(json):
async def handle_json(json, smth):
logger.debug('handle_json')
# logger.debug(session['userid'])
# url_for1 = url_for('my event', _external=True)
# logger.debug('url + ' + url_for1)
pipe.push(event_type='connected', **{'data': 'Connected'})
await pipe.push(event_type='connected', **{'data': 'Connected'})
logger.debug('received json 2: ' + str(json))


Expand All @@ -51,7 +52,7 @@ async def handle_my_custom_event(sid, json):
action = json.get('action')
if action == 'discovery':
await engine.will_start_test_discovery()
if action == 'run-tests':
if action == 'run-tests' or action == 'debug-tests':
if 'tests' not in json:
logger.error('run-tests command received, but no tests specified')
return
Expand All @@ -62,8 +63,13 @@ async def handle_my_custom_event(sid, json):
fqns.add(test['fqn'])

tests_to_run = all_tests.collect_by_fqn(fqns)
if action == 'debug-tests':
debugger_port = json.get('debugger_port')
debug_params = RemoteDebugParams(True, debugger_port)
else:
debug_params = RemoteDebugParams.disabled()

execution_pipeline.add_task(RunTestTask(tests_to_run))
execution_pipeline.add_task(RunTestTask(tests_to_run, debug_params))
if action == 'load-file':
filename = json.get('filename')
logger.debug('download_file ' + filename)
Expand Down Expand Up @@ -98,15 +104,15 @@ async def connect(sid, environ):
global thread
global watchdog_thread
logger.debug('Client test_connected')

connection_watchdog.connection_established()
await pipe.push(
event_type='connected',
**dict(
data='Connected test_connected',
engine_mode=engine.get_engine_mode(),
version=dict(
major=1,
minor=1,
minor=2,
)
)
)
Expand All @@ -123,4 +129,5 @@ async def connect(sid, environ):

@shared.sio.event
def disconnect(sid):
logger.debug('Client disconnected')
logger.debug('Client disconnected')
connection_watchdog.connection_lost()
16 changes: 16 additions & 0 deletions pycrunch/child_runtime/child_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class ChildRuntimeConfig:
def __init__(self):
self.load_pytest_plugins = False
self.enable_remote_debug = False
self.remote_debug_port = -1
self.runtime_engine = 'pytest'

def use_engine(self, new_engine):
self.runtime_engine = new_engine

def enable_remote_debugging(self, port_to_use):
self.enable_remote_debug = True
self.remote_debug_port = int(port_to_use)


child_config = ChildRuntimeConfig()
7 changes: 4 additions & 3 deletions pycrunch/child_runtime/client_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ def data_received(self, data):
if self.engine_to_use == 'django':
config.prepare_django()

runner_engine = PyTestRunnerEngine(config.load_pytest_plugins)
from pycrunch.child_runtime.child_config import child_config
runner_engine = PyTestRunnerEngine(child_config)

# should have env from pycrunch config heve
r = TestRunner(runner_engine, timeline)
r = TestRunner(runner_engine, timeline, child_config)
timeline.mark_event(f'Run: about to run tests')
try:
timeline.mark_event(f'Run: total tests planned: {len(msg.task.tests)}')
Expand Down Expand Up @@ -118,7 +119,7 @@ def mark_all_done(self):

def connection_lost(self, exc):
self.timeline.mark_event(f'TCP: Connection to server lost')
print(f'[{os.getpid()}] [task_id: {self.task_id}] - The connection to server lost')
print(f'[{os.getpid()}] [task_id: {self.task_id}] - The connection to parent pycrunch-engine process lost')
self.on_con_lost.set_result(True)

def error_received(self, exc):
Expand Down
19 changes: 11 additions & 8 deletions pycrunch/child_runtime/test_runner.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
from pycrunch.api.serializers import CoverageRun
from pycrunch.child_runtime.coverage_hal import CoverageAbstraction
from pycrunch.insights.variables_inspection import InsightTimeline, inject_timeline
from pycrunch.introspection.clock import clock
from pycrunch.runner.execution_result import ExecutionResult

DISABLE_COVERAGE = False

class TestRunner:
def __init__(self, runner_engine, timeline):
self.timeline = timeline
def __init__(self, runner_engine, timeline, child_config):
self.runner_engine = runner_engine
self.timeline = timeline
self.child_config = child_config

def run(self, tests):
self.timeline.mark_event('Run: inside run method')
from pycrunch.api.shared import timestamp
from pycrunch.introspection.clock import clock
from pycrunch.runner.interception import capture_stdout
from pycrunch.shared.models import TestMetadata
from pycrunch.shared.primitives import TestMetadata
self.timeline.mark_event('Run: inside run method - imports complete')

results = dict()
Expand All @@ -34,15 +34,18 @@ def run(self, tests):
# ---
# checked, there are 2x improvement for small files (0.06 vs 0.10, but still
# slow as before on 500+ tests in one file
cov = CoverageAbstraction(DISABLE_COVERAGE, self.timeline)
should_disable_coverage = DISABLE_COVERAGE
if self.child_config.enable_remote_debug:
should_disable_coverage = True
cov = CoverageAbstraction(should_disable_coverage, self.timeline)
cov.start()

with capture_stdout() as get_value:
time_start = timestamp()
time_start = clock.now()
self.timeline.mark_event('About to start test execution')
execution_result = self.runner_engine.run_test(metadata)
self.timeline.mark_event('Test execution complete, postprocessing results')
time_end = timestamp()
time_end = clock.now()
time_elapsed = time_end - time_start

cov.stop()
Expand Down
33 changes: 25 additions & 8 deletions pycrunch/crossprocess/multiprocess_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@

class MultiprocessTestRunner:

def __init__(self, timeout: Optional[float], timeline, test_run_scheduler):
def __init__(self, timeout: Optional[float], timeline, test_run_scheduler, remote_debug_params: "RemoteDebugParams"):
self.client_connections: List[TestRunnerServerProtocol] = []
self.completion_futures = []
self.timeline = timeline
self.timeout = timeout
self.results = None
self.test_run_scheduler = test_run_scheduler
self.remote_debug_params = remote_debug_params

def results_did_become_available(self, results):
logger.debug('results avail:')
Expand All @@ -44,7 +45,7 @@ async def run(self, tests):
logger.debug('Initializing subprocess...')
child_processes = []
for task in self.tasks:
child_processes.append(asyncio.create_subprocess_shell(self.get_command_line_for_child(port,task.id), cwd=config.working_directory, shell=True))
child_processes.append(self.create_child_subprocess(port, task))

logger.debug(f'Waiting for startup of {len(self.tasks)} subprocess task(s)')
subprocesses_results = await asyncio.gather(*child_processes)
Expand All @@ -59,11 +60,11 @@ async def run(self, tests):
try:
await asyncio.wait_for(
asyncio.gather(*child_waiters),
timeout=self.timeout
timeout=self.timeout_if_non_debug()
)
except (asyncio.TimeoutError, asyncio.CancelledError) as e:
timeout_reached = True
logger.warning(f'Reached execution timeout of {self.timeout} seconds. ')
logger.warning(f'Reached execution timeout of {self.timeout_if_non_debug()} seconds. ')
for _ in subprocesses_results:
try:
_.kill()
Expand All @@ -76,7 +77,7 @@ async def run(self, tests):
try:
demo_results = await asyncio.wait_for(
asyncio.gather(*self.completion_futures, return_exceptions=True),
timeout=self.timeout
timeout=self.timeout_if_non_debug()
)
except asyncio.TimeoutError as ex:
print(ex)
Expand All @@ -99,10 +100,26 @@ async def run(self, tests):
raise asyncio.TimeoutError('Test execution timeout.')
return _results

def create_child_subprocess(self, port, task):
# sys.executable is a full path to python.exe (or ./python) in current virtual environment
return asyncio.create_subprocess_exec(sys.executable, *self.get_command_line_for_child(port, task.id), cwd=config.working_directory)

def timeout_if_non_debug(self) -> Optional[float]:
if self.remote_debug_params.enabled:
return None
return self.timeout

def get_command_line_for_child(self, port, task_id):
engine_root = f' {config.engine_directory}{os.sep}pycrunch{os.sep}multiprocess_child_main.py '
hardcoded_path = engine_root + f'--engine={config.runtime_engine} --port={port} --task-id={task_id} --load-pytest-plugins={str(config.load_pytest_plugins).lower()}'
return sys.executable + hardcoded_path
results = []
results.append(f'{config.engine_directory}{os.sep}pycrunch{os.sep}multiprocess_child_main.py')
results.append(f'--engine={config.runtime_engine}')
results.append(f'--port={port}')
results.append(f'--task-id={task_id}')
results.append(f'--load-pytest-plugins={str(config.load_pytest_plugins).lower()}')
if self.remote_debug_params.enabled:
results.append(f'--enable-remote-debug')
results.append(f'--remote-debugger-port={self.remote_debug_params.port}')
return results

def create_server_protocol(self):
loop = asyncio.get_event_loop()
Expand Down
1 change: 1 addition & 0 deletions pycrunch/introspection/clock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

class Clock:
def now(self):
# floating point value in seconds
return time.perf_counter()


Expand Down
28 changes: 2 additions & 26 deletions pycrunch/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from pycrunch import web_ui
from pycrunch.session import config

from pycrunch.watchdog.connection_watchdog import connection_watchdog

package_directory = Path(__file__).parent
print(package_directory)
Expand Down Expand Up @@ -49,31 +49,6 @@ def run():
from pycrunch.api import shared
import pycrunch.api.socket_handlers

# sio = socketio.Server()


# settings = {
# "static_path": os.path.join(os.path.dirname(__file__), "front/dist"),
#
# }

# _Handler = socketio.get_tornado_handler(sio)
#
# class SocketHandler(_Handler):
# def check_origin(self, origin):
# return True
#
#
# app = tornado.web.Application(
# [
# (r'/ui/(.*)', tornado.web.StaticFileHandler, {"path" : 'front/dist'}),
# (r'/js/(.*)', tornado.web.StaticFileHandler, {"path" : 'front/dist/js'}),
# (r'/css/(.*)', tornado.web.StaticFileHandler, {"path" : 'front/dist/css'}),
# (r"/socket.io/", SocketHandler),
# ],
# # **settings
# # ... other application options
# )
app = web.Application()

sio.attach(app)
Expand All @@ -83,6 +58,7 @@ def run():


loop = asyncio.get_event_loop()
task = loop.create_task(connection_watchdog.watch_client_connection_loop())
loop.set_debug(True)
web.run_app(app, port=port, host='0.0.0.0')
# app.listen(port=port, address='0.0.0.0')
Expand Down
11 changes: 7 additions & 4 deletions pycrunch/multiprocess_child_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,18 @@ async def main():
parser.add_argument('--port', help='PyCrunch-Engine server port to connect')
parser.add_argument('--task-id', help='Id of task when multiple test runners ran at same time')
parser.add_argument('--load-pytest-plugins', help='If this is true, execution will be slower.')

parser.add_argument('--enable-remote-debug', action='store_true', help='If this is true, remote debug will be enabled on a --remote-debugger-port')
parser.add_argument('--remote-debugger-port', help='If remote debug is enabled, this will specify a port used to connect to PyCharm pudb')
args = parser.parse_args()
timeline.mark_event('ArgumentParser: parse_args completed')
engine_to_use = args.engine
if engine_to_use:
from pycrunch.session import config
config.runtime_engine_will_change(engine_to_use)
from pycrunch.child_runtime.child_config import child_config
child_config.use_engine(engine_to_use)
if args.load_pytest_plugins.lower() == 'true':
config.load_pytest_plugins = True
child_config.load_pytest_plugins = True
if args.enable_remote_debug:
child_config.enable_remote_debugging(args.remote_debugger_port)

timeline.mark_event('Before run')

Expand Down
4 changes: 2 additions & 2 deletions pycrunch/pipeline/file_modification_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pycrunch.discovery.simple import SimpleTestDiscovery
from pycrunch.pipeline import execution_pipeline
from pycrunch.pipeline.abstract_task import AbstractTask
from pycrunch.pipeline.run_test_task import RunTestTask
from pycrunch.pipeline.run_test_task import RunTestTask, RemoteDebugParams
from pycrunch.session import state
from pycrunch.session.combined_coverage import combined_coverage
from pycrunch.session.file_map import test_map
Expand Down Expand Up @@ -65,7 +65,7 @@ async def run(self):

tests_to_run = state.engine.all_tests.collect_by_fqn(execution_plan)
dirty_tests = self.consider_engine_mode(tests_to_run)
execution_pipeline.add_task(RunTestTask(dirty_tests))
execution_pipeline.add_task(RunTestTask(dirty_tests, RemoteDebugParams.disabled()))

pass;

Expand Down
Loading

0 comments on commit a3986e3

Please sign in to comment.