From 000b6eb4fa18b14e5f51f5e1c31e7aa9f2adff9f Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Fri, 28 Feb 2020 14:16:21 -0800 Subject: [PATCH 1/3] Add unittests for generic bindings --- .../activity_trigger/function.json | 10 ++ .../activity_trigger/main.py | 2 + .../orchestration_trigger/function.json | 10 ++ .../orchestration_trigger/main.py | 13 +++ .../foobar_as_bytes_no_anno/function.json | 17 ++++ .../foobar_as_bytes_no_anno/main.py | 3 + .../foobar_as_str_no_anno/function.json | 17 ++++ .../foobar_as_str_no_anno/main.py | 3 + .../foobar_implicit_output/function.json | 11 +++ .../foobar_implicit_output/main.py | 3 + .../foobar_with_no_datatype/function.json | 10 ++ .../foobar_with_no_datatype/main.py | 2 + .../unittests/test_mock_durable_functions.py | 60 ++++++++++++ .../unittests/test_mock_generic_functions.py | 91 +++++++++++++++++++ 14 files changed, 252 insertions(+) create mode 100644 tests/unittests/durable_functions/activity_trigger/function.json create mode 100644 tests/unittests/durable_functions/activity_trigger/main.py create mode 100644 tests/unittests/durable_functions/orchestration_trigger/function.json create mode 100644 tests/unittests/durable_functions/orchestration_trigger/main.py create mode 100644 tests/unittests/generic_functions/foobar_as_bytes_no_anno/function.json create mode 100644 tests/unittests/generic_functions/foobar_as_bytes_no_anno/main.py create mode 100644 tests/unittests/generic_functions/foobar_as_str_no_anno/function.json create mode 100644 tests/unittests/generic_functions/foobar_as_str_no_anno/main.py create mode 100644 tests/unittests/generic_functions/foobar_implicit_output/function.json create mode 100644 tests/unittests/generic_functions/foobar_implicit_output/main.py create mode 100644 tests/unittests/generic_functions/foobar_with_no_datatype/function.json create mode 100644 tests/unittests/generic_functions/foobar_with_no_datatype/main.py create mode 100644 tests/unittests/test_mock_durable_functions.py diff --git a/tests/unittests/durable_functions/activity_trigger/function.json b/tests/unittests/durable_functions/activity_trigger/function.json new file mode 100644 index 000000000..ebf8bfa62 --- /dev/null +++ b/tests/unittests/durable_functions/activity_trigger/function.json @@ -0,0 +1,10 @@ +{ + "scriptFile": "main.py", + "bindings": [ + { + "type": "activityTrigger", + "name": "input", + "direction": "in" + } + ] + } diff --git a/tests/unittests/durable_functions/activity_trigger/main.py b/tests/unittests/durable_functions/activity_trigger/main.py new file mode 100644 index 000000000..ab41921ce --- /dev/null +++ b/tests/unittests/durable_functions/activity_trigger/main.py @@ -0,0 +1,2 @@ +def main(input: str): + return input diff --git a/tests/unittests/durable_functions/orchestration_trigger/function.json b/tests/unittests/durable_functions/orchestration_trigger/function.json new file mode 100644 index 000000000..c8ef14a94 --- /dev/null +++ b/tests/unittests/durable_functions/orchestration_trigger/function.json @@ -0,0 +1,10 @@ +{ + "scriptFile": "main.py", + "bindings": [ + { + "type": "orchestrationTrigger", + "name": "context", + "direction": "in" + } + ] + } diff --git a/tests/unittests/durable_functions/orchestration_trigger/main.py b/tests/unittests/durable_functions/orchestration_trigger/main.py new file mode 100644 index 000000000..7ac02dfd7 --- /dev/null +++ b/tests/unittests/durable_functions/orchestration_trigger/main.py @@ -0,0 +1,13 @@ +# import azure.durable_functions as df + + +def generator_function(context): + final_result = yield context.call_activity('activity_trigger', 'foobar') + return final_result + + +def main(context): + # orchestrate = df.Orchestrator.create(generator_function) + # result = orchestrate(context) + # return result + return f'{context} :)' diff --git a/tests/unittests/generic_functions/foobar_as_bytes_no_anno/function.json b/tests/unittests/generic_functions/foobar_as_bytes_no_anno/function.json new file mode 100644 index 000000000..f0117f606 --- /dev/null +++ b/tests/unittests/generic_functions/foobar_as_bytes_no_anno/function.json @@ -0,0 +1,17 @@ +{ + "scriptFile": "main.py", + "bindings": [ + { + "type": "foobar", + "name": "input", + "direction": "in", + "dataType": "binary" + }, + { + "direction": "out", + "name": "$return", + "type": "foobar", + "dataType": "binary" + } + ] +} diff --git a/tests/unittests/generic_functions/foobar_as_bytes_no_anno/main.py b/tests/unittests/generic_functions/foobar_as_bytes_no_anno/main.py new file mode 100644 index 000000000..30548a4d3 --- /dev/null +++ b/tests/unittests/generic_functions/foobar_as_bytes_no_anno/main.py @@ -0,0 +1,3 @@ +# Input as bytes, without annotation +def main(input): + return input diff --git a/tests/unittests/generic_functions/foobar_as_str_no_anno/function.json b/tests/unittests/generic_functions/foobar_as_str_no_anno/function.json new file mode 100644 index 000000000..144593c6a --- /dev/null +++ b/tests/unittests/generic_functions/foobar_as_str_no_anno/function.json @@ -0,0 +1,17 @@ +{ + "scriptFile": "main.py", + "bindings": [ + { + "type": "foobar", + "name": "input", + "direction": "in", + "dataType": "string" + }, + { + "direction": "out", + "name": "$return", + "type": "foobar", + "dataType": "string" + } + ] +} diff --git a/tests/unittests/generic_functions/foobar_as_str_no_anno/main.py b/tests/unittests/generic_functions/foobar_as_str_no_anno/main.py new file mode 100644 index 000000000..b130a1761 --- /dev/null +++ b/tests/unittests/generic_functions/foobar_as_str_no_anno/main.py @@ -0,0 +1,3 @@ +# Input as string, without annotation +def main(input): + return input diff --git a/tests/unittests/generic_functions/foobar_implicit_output/function.json b/tests/unittests/generic_functions/foobar_implicit_output/function.json new file mode 100644 index 000000000..6f8a83ec0 --- /dev/null +++ b/tests/unittests/generic_functions/foobar_implicit_output/function.json @@ -0,0 +1,11 @@ +{ + "scriptFile": "main.py", + "bindings": [ + { + "type": "foobar", + "name": "input", + "direction": "in", + "dataType": "string" + } + ] +} diff --git a/tests/unittests/generic_functions/foobar_implicit_output/main.py b/tests/unittests/generic_functions/foobar_implicit_output/main.py new file mode 100644 index 000000000..78959586d --- /dev/null +++ b/tests/unittests/generic_functions/foobar_implicit_output/main.py @@ -0,0 +1,3 @@ +# Input as string, without annotation +def main(input: str): + return input diff --git a/tests/unittests/generic_functions/foobar_with_no_datatype/function.json b/tests/unittests/generic_functions/foobar_with_no_datatype/function.json new file mode 100644 index 000000000..4e49f1942 --- /dev/null +++ b/tests/unittests/generic_functions/foobar_with_no_datatype/function.json @@ -0,0 +1,10 @@ +{ + "scriptFile": "main.py", + "bindings": [ + { + "type": "foobar", + "name": "input", + "direction": "in" + } + ] +} diff --git a/tests/unittests/generic_functions/foobar_with_no_datatype/main.py b/tests/unittests/generic_functions/foobar_with_no_datatype/main.py new file mode 100644 index 000000000..dbe730061 --- /dev/null +++ b/tests/unittests/generic_functions/foobar_with_no_datatype/main.py @@ -0,0 +1,2 @@ +def main(input: str) -> str: + return input diff --git a/tests/unittests/test_mock_durable_functions.py b/tests/unittests/test_mock_durable_functions.py new file mode 100644 index 000000000..d85960302 --- /dev/null +++ b/tests/unittests/test_mock_durable_functions.py @@ -0,0 +1,60 @@ +from azure_functions_worker import protos +from azure_functions_worker import testutils + + +class TestDurableFunctions(testutils.AsyncTestCase): + durable_functions_dir = testutils.UNIT_TESTS_FOLDER / 'durable_functions' + + async def test_mock_activity_trigger(self): + async with testutils.start_mockhost( + script_root=self.durable_functions_dir) as host: + + func_id, r = await host.load_function('activity_trigger') + + self.assertEqual(r.response.function_id, func_id) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + + _, r = await host.invoke_function( + 'activity_trigger', [ + protos.ParameterBinding( + name='input', + data=protos.TypedData( + string='test' + ) + ) + ] + ) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + self.assertEqual( + r.response.return_value, + protos.TypedData(string='test') + ) + + async def test_mock_orchestration_trigger(self): + async with testutils.start_mockhost( + script_root=self.durable_functions_dir) as host: + + func_id, r = await host.load_function('orchestration_trigger') + + self.assertEqual(r.response.function_id, func_id) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + + _, r = await host.invoke_function( + 'orchestration_trigger', [ + protos.ParameterBinding( + name='context', + data=protos.TypedData( + string='Durable functions coming soon' + ) + ) + ] + ) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + self.assertEqual( + r.response.return_value, + protos.TypedData(string='Durable functions coming soon :)') + ) diff --git a/tests/unittests/test_mock_generic_functions.py b/tests/unittests/test_mock_generic_functions.py index 2cb895953..e5004e8ce 100644 --- a/tests/unittests/test_mock_generic_functions.py +++ b/tests/unittests/test_mock_generic_functions.py @@ -58,3 +58,94 @@ async def test_mock_generic_as_bytes(self): r.response.return_value, protos.TypedData(bytes=b'\x00\x01') ) + + async def test_mock_generic_as_str_no_anno(self): + async with testutils.start_mockhost( + script_root=self.generic_funcs_dir) as host: + + func_id, r = await host.load_function('foobar_as_str_no_anno') + + self.assertEqual(r.response.function_id, func_id) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + + _, r = await host.invoke_function( + 'foobar_as_str_no_anno', [ + protos.ParameterBinding( + name='input', + data=protos.TypedData( + string='test' + ) + ) + ] + ) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + self.assertEqual( + r.response.return_value, + protos.TypedData(string='test') + ) + + async def test_mock_generic_as_bytes_no_anno(self): + async with testutils.start_mockhost( + script_root=self.generic_funcs_dir) as host: + + func_id, r = await host.load_function('foobar_as_bytes_no_anno') + + self.assertEqual(r.response.function_id, func_id) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + + _, r = await host.invoke_function( + 'foobar_as_bytes_no_anno', [ + protos.ParameterBinding( + name='input', + data=protos.TypedData( + bytes=b'\x00\x01' + ) + ) + ] + ) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + self.assertEqual( + r.response.return_value, + protos.TypedData(bytes=b'\x00\x01') + ) + + async def test_mock_generic_should_not_support_implicit_output(self): + async with testutils.start_mockhost( + script_root=self.generic_funcs_dir) as host: + + func_id, r = await host.load_function('foobar_implicit_output') + + self.assertEqual(r.response.function_id, func_id) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + + _, r = await host.invoke_function( + 'foobar_as_bytes_no_anno', [ + protos.ParameterBinding( + name='input', + data=protos.TypedData( + bytes=b'\x00\x01' + ) + ) + ] + ) + # It should fail here, since generic binding requires + # $return statement in function.json to pass output + self.assertEqual(r.response.result.status, + protos.StatusResult.Failure) + + async def test_mock_generic_should_support_without_datatype(self): + async with testutils.start_mockhost( + script_root=self.generic_funcs_dir) as host: + + # It should fail here, since the generic binding requires datatype + # to be defined in function.json + func_id, r = await host.load_function('foobar_with_no_datatype') + + self.assertEqual(r.response.function_id, func_id) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) From 753da633babbcab01bcf719ceb6f811ac7410bbd Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Fri, 28 Feb 2020 14:39:18 -0800 Subject: [PATCH 2/3] Add a case for testing no annotation scenarios --- .../activity_trigger/main.py | 2 +- .../activity_trigger_no_anno/function.json | 10 +++++++ .../activity_trigger_no_anno/main.py | 2 ++ .../unittests/test_mock_durable_functions.py | 27 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/unittests/durable_functions/activity_trigger_no_anno/function.json create mode 100644 tests/unittests/durable_functions/activity_trigger_no_anno/main.py diff --git a/tests/unittests/durable_functions/activity_trigger/main.py b/tests/unittests/durable_functions/activity_trigger/main.py index ab41921ce..dbe730061 100644 --- a/tests/unittests/durable_functions/activity_trigger/main.py +++ b/tests/unittests/durable_functions/activity_trigger/main.py @@ -1,2 +1,2 @@ -def main(input: str): +def main(input: str) -> str: return input diff --git a/tests/unittests/durable_functions/activity_trigger_no_anno/function.json b/tests/unittests/durable_functions/activity_trigger_no_anno/function.json new file mode 100644 index 000000000..ebf8bfa62 --- /dev/null +++ b/tests/unittests/durable_functions/activity_trigger_no_anno/function.json @@ -0,0 +1,10 @@ +{ + "scriptFile": "main.py", + "bindings": [ + { + "type": "activityTrigger", + "name": "input", + "direction": "in" + } + ] + } diff --git a/tests/unittests/durable_functions/activity_trigger_no_anno/main.py b/tests/unittests/durable_functions/activity_trigger_no_anno/main.py new file mode 100644 index 000000000..96f42af54 --- /dev/null +++ b/tests/unittests/durable_functions/activity_trigger_no_anno/main.py @@ -0,0 +1,2 @@ +def main(input): + return input diff --git a/tests/unittests/test_mock_durable_functions.py b/tests/unittests/test_mock_durable_functions.py index d85960302..e2881b35d 100644 --- a/tests/unittests/test_mock_durable_functions.py +++ b/tests/unittests/test_mock_durable_functions.py @@ -32,6 +32,33 @@ async def test_mock_activity_trigger(self): protos.TypedData(string='test') ) + async def test_mock_activity_trigger_no_anno(self): + async with testutils.start_mockhost( + script_root=self.durable_functions_dir) as host: + + func_id, r = await host.load_function('activity_trigger_no_anno') + + self.assertEqual(r.response.function_id, func_id) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + + _, r = await host.invoke_function( + 'activity_trigger_no_anno', [ + protos.ParameterBinding( + name='input', + data=protos.TypedData( + bytes=b'\x34\x93\x04\x70' + ) + ) + ] + ) + self.assertEqual(r.response.result.status, + protos.StatusResult.Success) + self.assertEqual( + r.response.return_value, + protos.TypedData(bytes=b'\x34\x93\x04\x70') + ) + async def test_mock_orchestration_trigger(self): async with testutils.start_mockhost( script_root=self.durable_functions_dir) as host: From f4bf5949e5b35840da718ba35cbdc768dd7c2e78 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Fri, 28 Feb 2020 14:51:47 -0800 Subject: [PATCH 3/3] Fix generic binding without datatype test --- tests/unittests/test_mock_generic_functions.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/unittests/test_mock_generic_functions.py b/tests/unittests/test_mock_generic_functions.py index e5004e8ce..a8a69cd4f 100644 --- a/tests/unittests/test_mock_generic_functions.py +++ b/tests/unittests/test_mock_generic_functions.py @@ -142,10 +142,23 @@ async def test_mock_generic_should_support_without_datatype(self): async with testutils.start_mockhost( script_root=self.generic_funcs_dir) as host: - # It should fail here, since the generic binding requires datatype - # to be defined in function.json func_id, r = await host.load_function('foobar_with_no_datatype') self.assertEqual(r.response.function_id, func_id) self.assertEqual(r.response.result.status, protos.StatusResult.Success) + + _, r = await host.invoke_function( + 'foobar_with_no_datatype', [ + protos.ParameterBinding( + name='input', + data=protos.TypedData( + bytes=b'\x00\x01' + ) + ) + ] + ) + # It should fail here, since the generic binding requires datatype + # to be defined in function.json + self.assertEqual(r.response.result.status, + protos.StatusResult.Failure)