This is an automated email from the ASF dual-hosted git repository.

kirs pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new 54933b3  [ci][python] Add coverage check in CI (#6861)
54933b3 is described below

commit 54933b33e32424f92b2b0df1969a63c42582e078
Author: Jiajie Zhong <[email protected]>
AuthorDate: Wed Nov 17 09:46:40 2021 +0800

    [ci][python] Add coverage check in CI (#6861)
    
    * [ci] Add coverage check in CI
    
    * Coverage add dependent
    
    * Install pydolphinscheduler before run coverage
    
    * Up test coverage to 87% and down threshold to 85%
    
    * Fix code style
    
    * Add doc about coverage
---
 .github/workflows/py-ci.yml                        | 17 +++++
 .gitignore                                         |  9 +++
 .../{requirements_dev.txt => .coveragerc}          | 22 ++++--
 .../pydolphinscheduler/README.md                   | 14 ++++
 .../pydolphinscheduler/requirements_dev.txt        |  2 +
 .../src/pydolphinscheduler/constants.py            |  1 +
 .../src/pydolphinscheduler/utils/string.py         |  9 ++-
 .../tests/core/test_process_definition.py          | 17 +++++
 .../pydolphinscheduler/tests/core/test_task.py     | 74 +++++++++++++++++++
 .../pydolphinscheduler/tests/utils/test_date.py    |  9 ++-
 .../pydolphinscheduler/tests/utils/test_string.py  | 86 ++++++++++++++++++++++
 11 files changed, 249 insertions(+), 11 deletions(-)

diff --git a/.github/workflows/py-ci.yml b/.github/workflows/py-ci.yml
index 5b8e42a..fb8324e 100644
--- a/.github/workflows/py-ci.yml
+++ b/.github/workflows/py-ci.yml
@@ -78,3 +78,20 @@ jobs:
       - name: Run tests
         run: |
           pytest
+  coverage:
+    name: Tests coverage
+    needs:
+      - pytest
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python 3.7
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7
+      - name: Install Development Dependences
+        run: |
+          pip install -r requirements_dev.txt
+          pip install -e .
+      - name: Run Tests && Check coverage
+        run: coverage run && coverage report
diff --git a/.gitignore b/.gitignore
index 9b44df3..0ab7a2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,7 +49,16 @@ docker/build/apache-dolphinscheduler*
 dolphinscheduler-common/sql
 dolphinscheduler-common/test
 
+# ------------------
 # pydolphinscheduler
+# ------------------
+# Cache
 __pycache__/
+
+# Build
 build/
 *egg-info/
+
+# Test coverage
+.coverage
+htmlcov/
diff --git a/dolphinscheduler-python/pydolphinscheduler/requirements_dev.txt 
b/dolphinscheduler-python/pydolphinscheduler/.coveragerc
similarity index 66%
copy from dolphinscheduler-python/pydolphinscheduler/requirements_dev.txt
copy to dolphinscheduler-python/pydolphinscheduler/.coveragerc
index 49b4005..524cb73 100644
--- a/dolphinscheduler-python/pydolphinscheduler/requirements_dev.txt
+++ b/dolphinscheduler-python/pydolphinscheduler/.coveragerc
@@ -15,10 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# testting
-pytest~=6.2.5
-freezegun
-# code linting and formatting
-flake8
-flake8-docstrings
-flake8-black
+[run]
+command_line = -m pytest
+omit = 
+    # Ignore all test cases in tests/
+    tests/*
+    # TODO. Temporary ignore java_gateway file, because we could not find good 
way to test it.
+    src/pydolphinscheduler/java_gateway.py
+
+[report]
+# Don’t report files that are 100% covered
+skip_covered = True
+show_missing = True
+precision = 2
+# Report will fail when coverage under 90.00%
+fail_under = 85
diff --git a/dolphinscheduler-python/pydolphinscheduler/README.md 
b/dolphinscheduler-python/pydolphinscheduler/README.md
index 0cc36d7..a0cb748 100644
--- a/dolphinscheduler-python/pydolphinscheduler/README.md
+++ b/dolphinscheduler-python/pydolphinscheduler/README.md
@@ -132,6 +132,19 @@ To test locally, you could directly run pytest after set 
`PYTHONPATH`
 PYTHONPATH=src/ pytest
 ```
 
+We try to keep pydolphinscheduler usable through unit test coverage. 90% test 
coverage is our target, but for
+now, we require test coverage up to 85%, and each pull request leas than 85% 
would fail our CI step
+`Tests coverage`. We use [coverage][coverage] to check our test coverage, and 
you could check it locally by
+run command.
+
+```shell
+coverage run && coverage report
+```
+
+It would not only run unit test but also show each file coverage which cover 
rate less than 100%, and `TOTAL`
+line show you total coverage of you code. If your CI failed with coverage you 
could go and find some reason by
+this command output.
+
 <!-- content -->
 [pypi]: https://pypi.org/
 [dev-setup]: 
https://dolphinscheduler.apache.org/en-us/development/development-environment-setup.html
@@ -144,6 +157,7 @@ PYTHONPATH=src/ pytest
 [black]: https://black.readthedocs.io/en/stable/index.html
 [flake8]: https://flake8.pycqa.org/en/latest/index.html
 [black-editor]: 
https://black.readthedocs.io/en/stable/integrations/editors.html#pycharm-intellij-idea
+[coverage]: https://coverage.readthedocs.io/en/stable/
 <!-- badge -->
 [ga-py-test]: 
https://github.com/apache/dolphinscheduler/actions/workflows/py-ci.yml/badge.svg?branch=dev
 [ga]: https://github.com/apache/dolphinscheduler/actions
diff --git a/dolphinscheduler-python/pydolphinscheduler/requirements_dev.txt 
b/dolphinscheduler-python/pydolphinscheduler/requirements_dev.txt
index 49b4005..fa40e3c 100644
--- a/dolphinscheduler-python/pydolphinscheduler/requirements_dev.txt
+++ b/dolphinscheduler-python/pydolphinscheduler/requirements_dev.txt
@@ -18,6 +18,8 @@
 # testting
 pytest~=6.2.5
 freezegun
+# Test coverage
+coverage
 # code linting and formatting
 flake8
 flake8-docstrings
diff --git 
a/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/constants.py
 
b/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/constants.py
index d0d94c6..315a98c 100644
--- 
a/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/constants.py
+++ 
b/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/constants.py
@@ -94,6 +94,7 @@ class Delimiter(str):
     BAR = "-"
     DASH = "/"
     COLON = ":"
+    UNDERSCORE = "_"
 
 
 class Time(str):
diff --git 
a/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/utils/string.py
 
b/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/utils/string.py
index 3fb6a24..e7e781c 100644
--- 
a/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/utils/string.py
+++ 
b/dolphinscheduler-python/pydolphinscheduler/src/pydolphinscheduler/utils/string.py
@@ -17,20 +17,23 @@
 
 """String util function collections."""
 
+from pydolphinscheduler.constants import Delimiter
+
 
 def attr2camel(attr: str, include_private=True):
     """Covert class attribute name to camel case."""
     if include_private:
-        attr = attr.lstrip("_")
+        attr = attr.lstrip(Delimiter.UNDERSCORE)
     return snake2camel(attr)
 
 
 def snake2camel(snake: str):
     """Covert snake case to camel case."""
-    components = snake.split("_")
+    components = snake.split(Delimiter.UNDERSCORE)
     return components[0] + "".join(x.title() for x in components[1:])
 
 
 def class_name2camel(class_name: str):
     """Covert class name string to camel case."""
-    return class_name[0].lower() + class_name[1:]
+    class_name = class_name.lstrip(Delimiter.UNDERSCORE)
+    return class_name[0].lower() + snake2camel(class_name[1:])
diff --git 
a/dolphinscheduler-python/pydolphinscheduler/tests/core/test_process_definition.py
 
b/dolphinscheduler-python/pydolphinscheduler/tests/core/test_process_definition.py
index 0a028e8..930909a 100644
--- 
a/dolphinscheduler-python/pydolphinscheduler/tests/core/test_process_definition.py
+++ 
b/dolphinscheduler-python/pydolphinscheduler/tests/core/test_process_definition.py
@@ -18,6 +18,8 @@
 """Test process definition."""
 
 from datetime import datetime
+from typing import Any
+
 from pydolphinscheduler.utils.date import conv_to_schedule
 
 import pytest
@@ -135,6 +137,21 @@ def test__parse_datetime(val, expect):
         ), f"Function _parse_datetime with unexpect value by {val}."
 
 
[email protected](
+    "val",
+    [
+        20210101,
+        (2021, 1, 1),
+        {"year": "2021", "month": "1", "day": 1},
+    ],
+)
+def test__parse_datetime_not_support_type(val: Any):
+    """Test process definition function _parse_datetime not support type 
error."""
+    with ProcessDefinition(TEST_PROCESS_DEFINITION_NAME) as pd:
+        with pytest.raises(ValueError):
+            pd._parse_datetime(val)
+
+
 def test_process_definition_to_dict_without_task():
     """Test process definition function to_dict without task."""
     expect = {
diff --git a/dolphinscheduler-python/pydolphinscheduler/tests/core/test_task.py 
b/dolphinscheduler-python/pydolphinscheduler/tests/core/test_task.py
index ef5d363..4610337 100644
--- a/dolphinscheduler-python/pydolphinscheduler/tests/core/test_task.py
+++ b/dolphinscheduler-python/pydolphinscheduler/tests/core/test_task.py
@@ -18,8 +18,10 @@
 """Test Task class function."""
 
 from unittest.mock import patch
+import pytest
 
 from pydolphinscheduler.core.task import TaskParams, TaskRelation, Task
+from tests.testing.task import Task as testTask
 
 
 def test_task_params_to_dict():
@@ -93,3 +95,75 @@ def test_task_to_dict():
     ):
         task = Task(name=name, task_type=task_type, 
task_params=TaskParams(raw_script))
         assert task.to_dict() == expect
+
+
[email protected]("shift", ["<<", ">>"])
+def test_two_tasks_shift(shift: str):
+    """Test bit operator between tasks.
+
+    Here we test both `>>` and `<<` bit operator.
+    """
+    raw_script = "script"
+    upstream = testTask(
+        name="upstream", task_type=shift, task_params=TaskParams(raw_script)
+    )
+    downstream = testTask(
+        name="downstream", task_type=shift, task_params=TaskParams(raw_script)
+    )
+    if shift == "<<":
+        downstream << upstream
+    elif shift == ">>":
+        upstream >> downstream
+    else:
+        assert False, f"Unexpect bit operator type {shift}."
+    assert (
+        1 == len(upstream._downstream_task_codes)
+        and downstream.code in upstream._downstream_task_codes
+    ), "Task downstream task attributes error, downstream codes size or 
specific code failed."
+    assert (
+        1 == len(downstream._upstream_task_codes)
+        and upstream.code in downstream._upstream_task_codes
+    ), "Task upstream task attributes error, upstream codes size or upstream 
code failed."
+
+
[email protected](
+    "dep_expr, flag",
+    [
+        ("task << tasks", "upstream"),
+        ("tasks << task", "downstream"),
+        ("task >> tasks", "downstream"),
+        ("tasks >> task", "upstream"),
+    ],
+)
+def test_tasks_list_shift(dep_expr: str, flag: str):
+    """Test bit operator between task and sequence of tasks.
+
+    Here we test both `>>` and `<<` bit operator.
+    """
+    reverse_dict = {
+        "upstream": "downstream",
+        "downstream": "upstream",
+    }
+    task_type = "dep_task_and_tasks"
+    raw_script = "script"
+    task = testTask(
+        name="upstream", task_type=task_type, 
task_params=TaskParams(raw_script)
+    )
+    tasks = [
+        testTask(
+            name="downstream1", task_type=task_type, 
task_params=TaskParams(raw_script)
+        ),
+        testTask(
+            name="downstream2", task_type=task_type, 
task_params=TaskParams(raw_script)
+        ),
+    ]
+
+    # Use build-in function eval to simply test case and reduce duplicate code
+    eval(dep_expr)
+    direction_attr = f"_{flag}_task_codes"
+    reverse_direction_attr = f"_{reverse_dict[flag]}_task_codes"
+    assert 2 == len(getattr(task, direction_attr))
+    assert [t.code in getattr(task, direction_attr) for t in tasks]
+
+    assert all([1 == len(getattr(t, reverse_direction_attr)) for t in tasks])
+    assert all([task.code in getattr(t, reverse_direction_attr) for t in 
tasks])
diff --git 
a/dolphinscheduler-python/pydolphinscheduler/tests/utils/test_date.py 
b/dolphinscheduler-python/pydolphinscheduler/tests/utils/test_date.py
index 53ba478..648f2c4 100644
--- a/dolphinscheduler-python/pydolphinscheduler/tests/utils/test_date.py
+++ b/dolphinscheduler-python/pydolphinscheduler/tests/utils/test_date.py
@@ -63,7 +63,14 @@ def test_conv_from_str_success(src: str, expect: datetime) 
-> None:
 
 
 @pytest.mark.parametrize(
-    "src", ["2021-01-01 010101", "2021:01:01", "202111", "20210101010101"]
+    "src",
+    [
+        "2021-01-01 010101",
+        "2021:01:01",
+        "202111",
+        "20210101010101",
+        "2021:01:01 01:01:01",
+    ],
 )
 def test_conv_from_str_not_impl(src: str) -> None:
     """Test function conv_from_str fail case."""
diff --git 
a/dolphinscheduler-python/pydolphinscheduler/tests/utils/test_string.py 
b/dolphinscheduler-python/pydolphinscheduler/tests/utils/test_string.py
new file mode 100644
index 0000000..6942d7e
--- /dev/null
+++ b/dolphinscheduler-python/pydolphinscheduler/tests/utils/test_string.py
@@ -0,0 +1,86 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Test utils.string module."""
+
+from pydolphinscheduler.utils.string import attr2camel, snake2camel, 
class_name2camel
+import pytest
+
+
[email protected](
+    "snake, expect",
+    [
+        ("snake_case", "snakeCase"),
+        ("snake_123case", "snake123Case"),
+        ("snake_c_a_s_e", "snakeCASE"),
+        ("snake__case", "snakeCase"),
+        ("snake_case_case", "snakeCaseCase"),
+        ("_snake_case", "SnakeCase"),
+        ("__snake_case", "SnakeCase"),
+        ("Snake_case", "SnakeCase"),
+    ],
+)
+def test_snake2camel(snake: str, expect: str):
+    """Test function snake2camel, this is a base function for utils.string."""
+    assert expect == snake2camel(
+        snake
+    ), f"Test case {snake} do no return expect result {expect}."
+
+
[email protected](
+    "attr, expects",
+    [
+        # source attribute, (true expect, false expect),
+        ("snake_case", ("snakeCase", "snakeCase")),
+        ("snake_123case", ("snake123Case", "snake123Case")),
+        ("snake_c_a_s_e", ("snakeCASE", "snakeCASE")),
+        ("snake__case", ("snakeCase", "snakeCase")),
+        ("snake_case_case", ("snakeCaseCase", "snakeCaseCase")),
+        ("_snake_case", ("snakeCase", "SnakeCase")),
+        ("__snake_case", ("snakeCase", "SnakeCase")),
+        ("Snake_case", ("SnakeCase", "SnakeCase")),
+    ],
+)
+def test_attr2camel(attr: str, expects: tuple):
+    """Test function attr2camel."""
+    for idx, expect in enumerate(expects):
+        include_private = idx % 2 == 0
+        assert expect == attr2camel(
+            attr, include_private
+        ), f"Test case {attr} do no return expect result {expect} when 
include_private is {include_private}."
+
+
[email protected](
+    "class_name, expect",
+    [
+        ("snake_case", "snakeCase"),
+        ("snake_123case", "snake123Case"),
+        ("snake_c_a_s_e", "snakeCASE"),
+        ("snake__case", "snakeCase"),
+        ("snake_case_case", "snakeCaseCase"),
+        ("_snake_case", "snakeCase"),
+        ("_Snake_case", "snakeCase"),
+        ("__snake_case", "snakeCase"),
+        ("__Snake_case", "snakeCase"),
+        ("Snake_case", "snakeCase"),
+    ],
+)
+def test_class_name2camel(class_name: str, expect: str):
+    """Test function class_name2camel."""
+    assert expect == class_name2camel(
+        class_name
+    ), f"Test case {class_name} do no return expect result {expect}."

Reply via email to