diff --git a/.github/workflows/python-tests-coverage.yml b/.github/workflows/python-tests-coverage.yml new file mode 100644 index 0000000..9a4679b --- /dev/null +++ b/.github/workflows/python-tests-coverage.yml @@ -0,0 +1,49 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: ['3.10'] + fail-fast: false + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and run pytest + run: | + pip install pipenv + cd nurse + pipenv --python ${{ matrix.python-version }} install --dev + pipenv run pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=. tests/ | tee pytest-coverage.txt + - name: Pytest coverage comment + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-coverage-path: nurse/pytest-coverage.txt + title: Coverage Report + badge-title: Coverage Badge + hide-badge: false + hide-report: false + create-new-comment: false + hide-comment: false + report-only-changed-files: false + junitxml-path: nurse/pytest.xml + junitxml-title: JUnit Xml Summary \ No newline at end of file diff --git a/.github/workflows/python-tests-platforms.yml b/.github/workflows/python-tests-platforms.yml new file mode 100644 index 0000000..0dcf22f --- /dev/null +++ b/.github/workflows/python-tests-platforms.yml @@ -0,0 +1,34 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + python-version: ['2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10'] + fail-fast: false + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and run pytest + run: | + pip install pipenv + cd nurse + pipenv --python ${{ matrix.python-version }} install --dev + pipenv run pytest --html=report.html diff --git a/.gitignore b/.gitignore index 5262bd2..a637a50 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ Pipfile.lock **/__pycache__ **/assets *.html -**/.unotes \ No newline at end of file +**/.unotes +*.pyc +.coverage +pytest.xml +pytest-coverage.txt \ No newline at end of file diff --git a/nurse/.coveragerc b/nurse/.coveragerc new file mode 100644 index 0000000..3dbfbb4 --- /dev/null +++ b/nurse/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = tests/* \ No newline at end of file diff --git a/nurse/Pipfile b/nurse/Pipfile index c581d27..2fb475d 100644 --- a/nurse/Pipfile +++ b/nurse/Pipfile @@ -8,4 +8,5 @@ name = "pypi" [dev-packages] pytest = "*" pytest-html = "*" -pytest-mock = "*" \ No newline at end of file +pytest-mock = "*" +pytest-cov = "*" \ No newline at end of file diff --git a/nurse/README.md b/nurse/README.md index 535998c..95ebf8d 100644 --- a/nurse/README.md +++ b/nurse/README.md @@ -7,6 +7,7 @@ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ``` +[![Pytest](https://github.com/cyberark/atyourservice/actions/workflows/python-tests.yml/badge.svg)](https://github.com/cyberark/atyourservice/actions/workflows/python-tests.yml) - - - `nurse.py` is a command-line tool to retrieve diagnostic information from user's environments with their consent. diff --git a/nurse/nurse.py b/nurse/nurse.py index a8b5b2c..472efeb 100755 --- a/nurse/nurse.py +++ b/nurse/nurse.py @@ -3,7 +3,6 @@ import datetime import os import sys, traceback -import pwd from zipfile import ZipFile import json import copy diff --git a/nurse/tests/common.py b/nurse/tests/common.py index b8c15cf..083d3fb 100644 --- a/nurse/tests/common.py +++ b/nurse/tests/common.py @@ -30,13 +30,13 @@ def _get_user_input_mock(self, prompt, prefill): prompt = prompt.replace("-> ", "") # assert priority - expected_question = list(self.value.keys())[0] + expected_question = self.value[0][0] assert expected_question == prompt - user_answer = self.value[prompt] + user_answer = self.value[0][1] # it will not be called again - del self.value[prompt] + del self.value[0] return user_answer diff --git a/nurse/tests/test_all_questions_asked_in_priority.py b/nurse/tests/test_all_questions_asked_in_priority.py index e296892..2092850 100644 --- a/nurse/tests/test_all_questions_asked_in_priority.py +++ b/nurse/tests/test_all_questions_asked_in_priority.py @@ -1,21 +1,20 @@ -import pytest from .checklists import checklist_1 from .common import UserInputMocker, TestApp def test_all_questions_asked_in_order_priority_100(mocker): - user_input_dict = {"Question 7" : "Y", - "Question 1" : "Y", - "Followup to Question 1" : "Y", - "Followup to Followup Question 1": "Y", - "Question 8" : "Y", - "Followup to Question 8" : "Y", - "Question 9" : "Y", - "Question 10" : "Y", - "Question 4" : "Y", - "Question 5" : "Y", - "Question 2" : "Y", - "Question 3" : "Y", - "Question 6" : "Y",} + user_input_dict = [("Question 7" , "Y"), + ("Question 1" , "Y"), + ("Followup to Question 1" , "Y"), + ("Followup to Followup Question 1", "Y"), + ("Question 8" , "Y"), + ("Followup to Question 8" , "Y"), + ("Question 9" , "Y"), + ("Question 10" , "Y"), + ("Question 4" , "Y"), + ("Question 5" , "Y"), + ("Question 2" , "Y"), + ("Question 3" , "Y"), + ("Question 6" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) @@ -26,19 +25,19 @@ def test_all_questions_asked_in_order_priority_100(mocker): assert mock.isSuccess() def test_all_questions_asked_in_order_priority_200(mocker): - user_input_dict = {"Question 7" : "Y", - "Question 1" : "Y", - "Followup to Question 1" : "Y", - "Followup to Followup Question 1": "Y", - "Question 2" : "Y", - "Question 8" : "Y", - "Followup to Question 8" : "Y", - "Question 9" : "Y", - "Question 10" : "Y", - "Question 4" : "Y", - "Question 5" : "Y", - "Question 3" : "Y", - "Question 6" : "Y",} + user_input_dict = [("Question 7" , "Y"), + ("Question 1" , "Y"), + ("Followup to Question 1" , "Y"), + ("Followup to Followup Question 1", "Y"), + ("Question 2" , "Y"), + ("Question 8" , "Y"), + ("Followup to Question 8" , "Y"), + ("Question 9" , "Y"), + ("Question 10" , "Y"), + ("Question 4" , "Y"), + ("Question 5" , "Y"), + ("Question 3" , "Y"), + ("Question 6" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) @@ -49,19 +48,19 @@ def test_all_questions_asked_in_order_priority_200(mocker): assert mock.isSuccess() def test_all_questions_asked_in_order_priority_50(mocker): - user_input_dict = {"Question 7" : "Y", - "Question 8" : "Y", - "Followup to Question 8" : "Y", - "Question 9" : "Y", - "Question 10" : "Y", - "Question 4" : "Y", - "Question 5" : "Y", - "Question 1" : "Y", - "Followup to Question 1" : "Y", - "Followup to Followup Question 1": "Y", - "Question 2" : "Y", - "Question 3" : "Y", - "Question 6" : "Y",} + user_input_dict = [("Question 7" , "Y"), + ("Question 8" , "Y"), + ("Followup to Question 8" , "Y"), + ("Question 9" , "Y"), + ("Question 10" , "Y"), + ("Question 4" , "Y"), + ("Question 5" , "Y"), + ("Question 1" , "Y"), + ("Followup to Question 1" , "Y"), + ("Followup to Followup Question 1", "Y"), + ("Question 2" , "Y"), + ("Question 3" , "Y"), + ("Question 6" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) @@ -72,19 +71,19 @@ def test_all_questions_asked_in_order_priority_50(mocker): assert mock.isSuccess() def test_all_questions_asked_in_order_priority_10(mocker): - user_input_dict = {"Question 8" : "Y", - "Followup to Question 8" : "Y", - "Question 9" : "Y", - "Question 10" : "Y", - "Question 4" : "Y", - "Question 5" : "Y", - "Question 7" : "Y", - "Question 1" : "Y", - "Followup to Question 1" : "Y", - "Followup to Followup Question 1": "Y", - "Question 2" : "Y", - "Question 3" : "Y", - "Question 6" : "Y",} + user_input_dict = [("Question 8" , "Y"), + ("Followup to Question 8" , "Y"), + ("Question 9" , "Y"), + ("Question 10" , "Y"), + ("Question 4" , "Y"), + ("Question 5" , "Y"), + ("Question 7" , "Y"), + ("Question 1" , "Y"), + ("Followup to Question 1" , "Y"), + ("Followup to Followup Question 1", "Y"), + ("Question 2" , "Y"), + ("Question 3" , "Y"), + ("Question 6" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) @@ -95,19 +94,19 @@ def test_all_questions_asked_in_order_priority_10(mocker): assert mock.isSuccess() def test_all_questions_asked_in_order_priority_300(mocker): - user_input_dict = {"Question 7" : "Y", - "Question 1" : "Y", - "Followup to Question 1" : "Y", - "Followup to Followup Question 1": "Y", - "Question 2" : "Y", - "Question 8" : "Y", - "Followup to Question 8" : "Y", - "Question 3" : "Y", - "Question 9" : "Y", - "Question 10" : "Y", - "Question 4" : "Y", - "Question 5" : "Y", - "Question 6" : "Y",} + user_input_dict = [("Question 7" , "Y"), + ("Question 1" , "Y"), + ("Followup to Question 1" , "Y"), + ("Followup to Followup Question 1", "Y"), + ("Question 2" , "Y"), + ("Question 8" , "Y"), + ("Followup to Question 8" , "Y"), + ("Question 3", "Y"), + ("Question 9" , "Y"), + ("Question 10" , "Y"), + ("Question 4" , "Y"), + ("Question 5" , "Y"), + ("Question 6" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) @@ -118,16 +117,16 @@ def test_all_questions_asked_in_order_priority_300(mocker): assert mock.isSuccess() def test_all_questions_asked_in_order_priority_300_no_followup(mocker): - user_input_dict = {"Question 7" : "Y", - "Question 1" : "N", - "Question 2" : "Y", - "Question 8" : "N", - "Question 3" : "Y", - "Question 9" : "Y", - "Question 10" : "Y", - "Question 4" : "Y", - "Question 5" : "Y", - "Question 6" : "Y",} + user_input_dict = [("Question 7" , "Y"), + ("Question 1" , "N"), + ("Question 2" , "Y"), + ("Question 8" , "N"), + ("Question 3" , "Y"), + ("Question 9" , "Y"), + ("Question 10" , "Y"), + ("Question 4" , "Y"), + ("Question 5" , "Y"), + ("Question 6" , "Y"),] mock = UserInputMocker(user_input_dict).patch(mocker) diff --git a/nurse/tests/test_commands_output.py b/nurse/tests/test_commands_output.py index 7dbd5c5..3eb1d58 100644 --- a/nurse/tests/test_commands_output.py +++ b/nurse/tests/test_commands_output.py @@ -7,7 +7,7 @@ def test_output_into_file(mocker): expected_archived_paths = ["a/b/c/echo_command.txt", "b/c/d/python_command.txt"] - user_input_dict = {"Question 1" : "Y"} + user_input_dict = [("Question 1" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) @@ -44,7 +44,7 @@ def test_output_into_file(mocker): def test_output_into_file_and_inline(mocker): expected_archived_paths = ["b/c/d/python_command.txt"] - user_input_dict = {"Question 1" : "Y"} + user_input_dict = [("Question 1" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) diff --git a/nurse/tests/test_different_checklist_structures.py b/nurse/tests/test_different_checklist_structures.py index a64440b..7e6e3a6 100644 --- a/nurse/tests/test_different_checklist_structures.py +++ b/nurse/tests/test_different_checklist_structures.py @@ -13,11 +13,11 @@ def test_checklist_no_questions(mocker): assert mock.isSuccess() def test_checklist_numerous_followups(mocker): - user_input_dict = {"Question 1" : "Y", - "Followup 1" : "Y", - "Followup 2" : "Y", - "Followup 3" : "Y", - "Followup of Followup 3" : "Y"} + user_input_dict = [("Question 1" , "Y"), + ("Followup 1" , "Y"), + ("Followup 2" , "Y"), + ("Followup 3" , "Y"), + ("Followup of Followup 3" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) @@ -27,10 +27,10 @@ def test_checklist_numerous_followups(mocker): assert mock.isSuccess() def test_checklist_nested_followups(mocker): - user_input_dict = {"Question 1" : "Y", - "Followup 1" : "Y", - "Followup 2" : "Y", - "Followup 3" : "Y"} + user_input_dict = [("Question 1" , "Y"), + ("Followup 1" , "Y"), + ("Followup 2" , "Y"), + ("Followup 3" , "Y")] mock = UserInputMocker(user_input_dict).patch(mocker) diff --git a/nurse/tests/test_extra_info_appears.py b/nurse/tests/test_extra_info_appears.py index ca97b71..782254f 100644 --- a/nurse/tests/test_extra_info_appears.py +++ b/nurse/tests/test_extra_info_appears.py @@ -1,4 +1,3 @@ -import pytest from nurse import nurse, blank_archiver, checklistjson, asciicolors, NO_DESCRIPTION_MESSAGE from .checklists import checklist_1 from .common import UserInputMocker @@ -8,10 +7,10 @@ def _get_user_input_mock(self, prompt, prefill): prompt = prompt.replace("-> ", "") # assert priority - expected_question = list(self.value.keys())[0] + expected_question = self.value[0][0] assert expected_question in prompt - info = self.value[expected_question] + info = self.value[0][1] for text in info["expected_text"]: assert text in prompt @@ -19,7 +18,7 @@ def _get_user_input_mock(self, prompt, prefill): user_answer = info["user_input"] # it will not be called again - del self.value[expected_question] + del self.value[0] return user_answer @@ -40,19 +39,19 @@ def run(self): ExtraInfoTestApp.__test__ = False def test_all_questions_asked_with_extra_info(mocker): - user_input_dict = {"Question 7" : {"expected_text": ["Description 7"], "user_input": "Y"}, - "Question 1" : {"expected_text": ["Description 1"], "user_input": "Y"}, - "Followup to Question 1" : {"expected_text": [NO_DESCRIPTION_MESSAGE], "user_input": "Y"}, - "Followup to Followup Question 1": {"expected_text": ["Description 20"], "user_input": "Y"}, - "Question 8" : {"expected_text": ["Description 8"], "user_input": "Y"}, - "Followup to Question 8" : {"expected_text": ["Followup Description 8"], "user_input": "Y"}, - "Question 9" : {"expected_text": ["Description 9"], "user_input": "Y"}, - "Question 10" : {"expected_text": [NO_DESCRIPTION_MESSAGE], "user_input":"Y"}, - "Question 4" : {"expected_text": ["Command 1 Description"], "user_input": "Y"}, - "Question 5" : {"expected_text": ["Command 2 Description", "Command 3 Description"], "user_input": "Y"}, - "Question 2" : {"expected_text": ["Description 2"], "user_input": "Y"}, - "Question 3" : {"expected_text": ["Description 3"], "user_input": "Y"}, - "Question 6" : {"expected_text": ["Description 6"], "user_input": "Y"}} + user_input_dict = [("Question 7", {"expected_text": ["Description 7"], "user_input": "Y"}), + ("Question 1", {"expected_text": ["Description 1"], "user_input": "Y"}), + ("Followup to Question 1", {"expected_text": [NO_DESCRIPTION_MESSAGE], "user_input": "Y"}), + ("Followup to Followup Question 1", {"expected_text": ["Description 20"], "user_input": "Y"}), + ("Question 8", {"expected_text": ["Description 8"], "user_input": "Y"}), + ("Followup to Question 8", {"expected_text": ["Followup Description 8"], "user_input": "Y"}), + ("Question 9", {"expected_text": ["Description 9"], "user_input": "Y"}), + ("Question 10", {"expected_text": [NO_DESCRIPTION_MESSAGE], "user_input":"Y"}), + ("Question 4", {"expected_text": ["Command 1 Description"], "user_input": "Y"}), + ("Question 5", {"expected_text": ["Command 2 Description", "Command 3 Description"], "user_input": "Y"}), + ("Question 2", {"expected_text": ["Description 2"], "user_input": "Y"}), + ("Question 3", {"expected_text": ["Description 3"], "user_input": "Y"}), + ("Question 6", {"expected_text": ["Description 6"], "user_input": "Y"})] mock = UserInputExtraInfoMocker(user_input_dict).patch(mocker) diff --git a/nurse/tests/test_files_and_directories_retrieval.py b/nurse/tests/test_files_and_directories_retrieval.py index 365cd63..44cc401 100644 --- a/nurse/tests/test_files_and_directories_retrieval.py +++ b/nurse/tests/test_files_and_directories_retrieval.py @@ -6,6 +6,15 @@ import copy from nurse import nurse, string_to_delta from datetime import datetime, timedelta +import sys +import time + +if sys.version_info[0] == 2: + import errno + + class FileExistsError(OSError): + def __init__(self, msg): + super(FileExistsError, self).__init__(errno.EEXIST, msg) class FilesystemObject(object): def create(self, in_path="."): @@ -21,7 +30,7 @@ def create(self, in_path="."): try: os.mkdir(self.my_path) - except FileExistsError as fe: + except (FileExistsError, OSError) as fe: pass for obj in self.contains: @@ -36,13 +45,17 @@ def __init__(self, name=None, modified_time=None): self.modified_time = modified_time def create(self, in_path="."): + # use this instead of datetime.timestamp to support Python 2.7 + def to_seconds(date): + return time.mktime(date.timetuple()) + self.my_path = os.path.join(in_path, self.name) with open(self.my_path, "w") as fd: fd.write("This is a mock file used for testing purposes.") - new_access_time = datetime.timestamp(datetime.now()) - new_modified_time = datetime.timestamp(self.modified_time) + new_access_time = to_seconds(datetime.now()) + new_modified_time = to_seconds(self.modified_time) os.utime(self.my_path, (new_access_time, new_modified_time)) @@ -53,7 +66,7 @@ def str_to_datetime(string): return datetime.today() + string_to_delta(string) def test_checklist_nested_files(mocker): - user_input_dict = {"Question 1" : "Y"} + user_input_dict = [("Question 1" , "Y")] expected_archived_paths = ["./mock_files/New1.txt", "./mock_files/test_1/New2.txt", @@ -101,7 +114,7 @@ def test_checklist_nested_files(mocker): Tree.delete() def test_checklist_hours_filter(mocker): - user_input_dict = {"Question 1" : "Y"} + user_input_dict = [("Question 1" , "Y")] expected_archived_paths = ["./mock_files/New1.txt"]