Merge branch 'develop' into anfranc-nexilus/feature/add-notebook-params-and-debugger

This commit is contained in:
Andrew Francisque 2021-09-17 10:18:25 -04:00 коммит произвёл GitHub
Родитель 3ad9da810c 1683ea1b53
Коммит 72ef829712
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 254 добавлений и 7 удалений

2
.github/workflows/pythonpackage.yml поставляемый
Просмотреть файл

@ -34,5 +34,5 @@ jobs:
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest - name: Test with pytest
run: | run: |
pip install pytest pip install pytest py4j
pytest pytest

Просмотреть файл

@ -319,6 +319,7 @@ FLAGS
--notebook_params Allows parameters to be passed from the CLI tool to the test notebook. From the --notebook_params Allows parameters to be passed from the CLI tool to the test notebook. From the
notebook, these parameters can then be accessed by the notebook using notebook, these parameters can then be accessed by the notebook using
the 'dbutils.widgets.get('key')' syntax. the 'dbutils.widgets.get('key')' syntax.
``` ```
__Note:__ You can also use flags syntax for POSITIONAL ARGUMENTS __Note:__ You can also use flags syntax for POSITIONAL ARGUMENTS

Просмотреть файл

@ -3,9 +3,13 @@ Copyright (c) Microsoft Corporation.
Licensed under the MIT license. Licensed under the MIT license.
""" """
from .pickleserializable import PickleSerializable
import pickle
import base64 import base64
import pickle
from py4j.protocol import Py4JJavaError
from .pickleserializable import PickleSerializable
def get_test_results(): def get_test_results():
return TestResults() return TestResults()
@ -30,6 +34,9 @@ class TestResults(PickleSerializable):
self.total_execution_time = total_execution_time self.total_execution_time = total_execution_time
def serialize(self): def serialize(self):
for i in self.results:
if isinstance(i.exception, Py4JJavaError):
i.exception = Exception(str(i.exception))
bin_data = pickle.dumps(self) bin_data = pickle.dumps(self)
return str(base64.encodebytes(bin_data), "utf-8") return str(base64.encodebytes(bin_data), "utf-8")

56
runtime/runner.py Normal file
Просмотреть файл

@ -0,0 +1,56 @@
"""
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
"""
import common.scheduler as scheduler
from common.testexecresults import TestExecResults
from common.testresult import TestResults
from runtime.nutterfixture import NutterFixture
class NutterFixtureParallelRunner(object):
"""Helper class to execute tests in parallel."""
def __init__(self, num_of_workers=1):
"""Initialize the runner.
Args:
num_of_workers (int): number of parallel workers.
"""
self.tests = []
self.num_of_workers = num_of_workers
def add_test_fixture(self, fixture):
"""Add a test to the list of tests to run.
Args:
fixture (NutterFixture): the test to add.
"""
if not isinstance(fixture, NutterFixture):
raise TypeError("fixture must be of type NutterFixture")
self.tests.append(fixture)
def execute(self):
"""Execute the tests."""
sched = scheduler.get_scheduler(self.num_of_workers)
for i in self.tests:
sched.add_function(i.execute_tests)
results = sched.run_and_wait()
return self._collect_results(results)
def _collect_results(self, results):
"""Collect all results in a single TestExecResults object."""
all_results = TestResults()
for funcres in results:
if funcres.func_result is not None:
for testres in funcres.func_result.test_results.results:
all_results.append(testres)
return TestExecResults(all_results)

Просмотреть файл

@ -3,11 +3,15 @@ Copyright (c) Microsoft Corporation.
Licensed under the MIT license. Licensed under the MIT license.
""" """
import pytest
import json
from common.testresult import TestResults, TestResult
import pickle
import base64 import base64
import json
import pickle
import mock
import pytest
from common.testresult import TestResult, TestResults
from py4j.protocol import Py4JError, Py4JJavaError
def test__testresults_append__type_not_testresult__throws_error(): def test__testresults_append__type_not_testresult__throws_error():
# Arrange # Arrange
@ -74,6 +78,21 @@ def test__deserialize__invalid_pickle_data__throws_Exception():
with pytest.raises(Exception): with pytest.raises(Exception):
test_results.deserialize(invalid_pickle) test_results.deserialize(invalid_pickle)
def test__deserialize__p4jjavaerror__is_serializable_and_deserializable():
# Arrange
test_results = TestResults()
py4j_exception = get_mock_py4j_error_exception(get_mock_gateway_client(), mock_target_id="o123")
test_results.append(TestResult("Test Name", True, 1, [], py4j_exception))
with mock.patch('py4j.protocol.get_return_value') as mock_get_return_value:
mock_get_return_value.return_value = 'foo'
serialized_data = test_results.serialize()
deserialized_data = TestResults().deserialize(serialized_data)
assert test_results == deserialized_data
def test__eq__test_results_equal_but_not_same_ref__are_equal(): def test__eq__test_results_equal_but_not_same_ref__are_equal():
# Arrange # Arrange
@ -139,3 +158,25 @@ def test__deserialize__data_is_base64_str__can_deserialize():
test_results_from_data = TestResults().deserialize(serialized_str) test_results_from_data = TestResults().deserialize(serialized_str)
assert test_results == test_results_from_data assert test_results == test_results_from_data
def get_mock_gateway_client():
mock_client = mock.Mock()
mock_client.send_command.return_value = "0"
mock_client.converters = []
mock_client.is_connected.return_value = True
mock_client.deque = mock.Mock()
return mock_client
def get_mock_java_object(mock_client, mock_target_id):
mock_java_object = mock.Mock()
mock_java_object._target_id = mock_target_id
mock_java_object._gateway_client = mock_client
return mock_java_object
def get_mock_py4j_error_exception(mock_client, mock_target_id):
mock_java_object = get_mock_java_object(mock_client, mock_target_id)
mock_errmsg = "An error occurred while calling {}.load.".format(mock_target_id)
return Py4JJavaError(mock_errmsg, java_exception=mock_java_object)

Просмотреть файл

@ -0,0 +1,142 @@
"""
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
"""
import time
import pytest
from runtime.nutterfixture import NutterFixture
from runtime.runner import NutterFixtureParallelRunner
test_cases = [
(1, 1),
(1, 2),
(2, 1),
(2, 2),
(3, 1),
(3, 2),
(3, 3)
]
@pytest.mark.parametrize('num_of_tests, num_of_workers', test_cases)
def test__execute_tests__x_tests_x_workers__results_ok(num_of_tests, num_of_workers):
# Assemble list of tests
runner = NutterFixtureParallelRunner(num_of_workers)
for i in range(num_of_tests):
test_case = RunnerTestFixture()
runner.add_test_fixture(test_case)
# Execute tests
results = runner.execute()
# Assert results
assert len(results.test_results.results) == num_of_tests
assert results.test_results.passed() == True
def test__execute_tests__3_tests_in_sequence_with_failed_assertion__results_ok():
# Arrange
tests = [
RunnerTestFixture(),
RunnerTestFixtureFailAssert(),
RunnerTestFixture()
]
runner = NutterFixtureParallelRunner()
for i in tests:
runner.add_test_fixture(i)
# Act
results = runner.execute()
# Assert
assert len(results.test_results.results) == len(tests)
assert results.test_results.results[0].passed == True
assert results.test_results.results[1].passed == False
assert results.test_results.results[2].passed == True
def test__execute_tests__3_tests_in_sequence_with_run_exception__results_ok():
# Arrange
tests = [
RunnerTestFixture(),
RunnerTestFixtureRunException(),
RunnerTestFixture()
]
runner = NutterFixtureParallelRunner()
for i in tests:
runner.add_test_fixture(i)
# Act
results = runner.execute()
# Assert
assert len(results.test_results.results) == len(tests)
assert results.test_results.results[0].passed == True
assert results.test_results.results[1].passed == False
assert results.test_results.results[2].passed == True
def test__execute_tests__3_tests_in_sequence_with_exec_exception__results_ok():
# Arrange
tests = [
RunnerTestFixture(),
RunnerTestFixtureExecuteException(),
RunnerTestFixture()
]
runner = NutterFixtureParallelRunner()
for i in tests:
runner.add_test_fixture(i)
# Act
results = runner.execute()
# Assert
assert len(results.test_results.results) == len(tests) - 1
assert results.test_results.results[0].passed == True
assert results.test_results.results[1].passed == True
class RunnerTestFixture(NutterFixture):
def before_test(self):
pass
def run_test(self):
pass
def assertion_test(self):
assert 1 == 1
def after_test(self):
pass
class RunnerTestFixtureFailAssert(NutterFixture):
def before_test(self):
pass
def run_test(self):
pass
def assertion_test(self):
assert 1 != 1
def after_test(self):
pass
class RunnerTestFixtureRunException(NutterFixture):
def before_test(self):
pass
def run_test(self):
raise(Exception())
def assertion_test(self):
assert 1 == 1
def after_test(self):
pass
class RunnerTestFixtureExecuteException(NutterFixture):
def execute_tests(self):
raise(Exception())