test_example.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. """Test example."""
  18. import ast
  19. import importlib
  20. from unittest.mock import patch
  21. import pytest
  22. from tests.testing.constants import task_without_example
  23. from tests.testing.path import get_all_examples, get_tasks
  24. from tests.testing.task import Task
  25. process_definition_name = set()
  26. def import_module(script_name, script_path):
  27. """Import and run example module in examples directory."""
  28. spec = importlib.util.spec_from_file_location(script_name, script_path)
  29. module = importlib.util.module_from_spec(spec)
  30. spec.loader.exec_module(module)
  31. return module
  32. def test_task_without_example():
  33. """Test task which without example.
  34. Avoiding add new type of tasks but without adding example describe how to use it.
  35. """
  36. # We use example/tutorial.py as shell task example
  37. ignore_name = {"__init__.py", "shell.py", "func_wrap.py"}
  38. all_tasks = {task.stem for task in get_tasks(ignore_name=ignore_name)}
  39. have_example_tasks = set()
  40. start = "task_"
  41. end = "_example"
  42. for ex in get_all_examples():
  43. stem = ex.stem
  44. if stem.startswith(start) and stem.endswith(end):
  45. task_name = stem.replace(start, "").replace(end, "")
  46. have_example_tasks.add(task_name)
  47. assert all_tasks.difference(have_example_tasks) == task_without_example
  48. @pytest.fixture
  49. def setup_and_teardown_for_stuff():
  50. """Fixture of py.test handle setup and teardown."""
  51. yield
  52. global process_definition_name
  53. process_definition_name = set()
  54. def submit_check_without_same_name(self):
  55. """Side effect for verifying process definition name and adding it to global variable."""
  56. if self.name in process_definition_name:
  57. raise ValueError(
  58. "Example process definition should not have same name, but get duplicate name: %s",
  59. self.name,
  60. )
  61. submit_add_process_definition(self)
  62. def submit_add_process_definition(self):
  63. """Side effect for adding process definition name to global variable."""
  64. process_definition_name.add(self.name)
  65. def test_example_basic():
  66. """Test example basic information.
  67. Which including:
  68. * File extension name is `.py`
  69. * All example except `tutorial.py` is end with keyword "_example"
  70. * All example must have not empty `__doc__`.
  71. """
  72. for ex in get_all_examples():
  73. # All files in example is python script
  74. assert (
  75. ex.suffix == ".py"
  76. ), f"We expect all examples is python script, but get {ex.name}."
  77. # All except tutorial and __init__ is end with keyword "_example"
  78. if ex.stem not in ("tutorial", "tutorial_decorator") and ex.stem != "__init__":
  79. assert ex.stem.endswith(
  80. "_example"
  81. ), f"We expect all examples script end with keyword '_example', but get {ex.stem}."
  82. # All files have __doc__
  83. tree = ast.parse(ex.read_text())
  84. example_doc = ast.get_docstring(tree, clean=False)
  85. assert (
  86. example_doc is not None
  87. ), f"We expect all examples have __doc__, but {ex.name} do not."
  88. @patch("pydolphinscheduler.core.process_definition.ProcessDefinition.start")
  89. @patch(
  90. "pydolphinscheduler.core.process_definition.ProcessDefinition.submit",
  91. side_effect=submit_check_without_same_name,
  92. autospec=True,
  93. )
  94. @patch(
  95. "pydolphinscheduler.core.task.Task.gen_code_and_version",
  96. # Example bulk_create_example.py would create workflow dynamic by :func:`get_one_task_by_name`
  97. # and would raise error in :func:`get_one_task_by_name` if we return constant value
  98. # using :arg:`return_value`
  99. side_effect=Task("test_example", "test_example").gen_code_and_version,
  100. )
  101. def test_example_process_definition_without_same_name(
  102. mock_code_version, mock_submit, mock_start
  103. ):
  104. """Test all examples file without same process definition's name.
  105. Our process definition would compete with others if we have same process definition name. It will make
  106. different between actually workflow and our workflow-as-code file which make users feel strange.
  107. """
  108. for ex in get_all_examples():
  109. # We use side_effect `submit_check_without_same_name` overwrite :func:`submit`
  110. # and check whether it have duplicate name or not
  111. import_module(ex.name, str(ex))
  112. assert True
  113. @patch("pydolphinscheduler.core.process_definition.ProcessDefinition.start")
  114. @patch(
  115. "pydolphinscheduler.core.process_definition.ProcessDefinition.submit",
  116. side_effect=submit_add_process_definition,
  117. autospec=True,
  118. )
  119. @patch(
  120. "pydolphinscheduler.core.task.Task.gen_code_and_version",
  121. # Example bulk_create_example.py would create workflow dynamic by :func:`get_one_task_by_name`
  122. # and would raise error in :func:`get_one_task_by_name` if we return constant value
  123. # using :arg:`return_value`
  124. side_effect=Task("test_example", "test_example").gen_code_and_version,
  125. )
  126. def test_file_name_in_process_definition(mock_code_version, mock_submit, mock_start):
  127. """Test example file name in example definition name.
  128. We should not directly assert equal, because some of the examples contain
  129. more than one process definition.
  130. """
  131. global process_definition_name
  132. for ex in get_all_examples():
  133. # Skip __init__ file
  134. if ex.stem == "__init__":
  135. continue
  136. # Skip bulk_create_example check, cause it contain multiple workflow and
  137. # without one named bulk_create_example
  138. if ex.stem == "bulk_create_example":
  139. continue
  140. process_definition_name = set()
  141. assert ex.stem not in process_definition_name
  142. import_module(ex.name, str(ex))
  143. assert ex.stem in process_definition_name