diff --git a/.appveyor.yml b/.appveyor.yml index 2c046836..d8f2dcc9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,8 +6,9 @@ branches: skip_tags: true max_jobs: 1 image: +- Visual Studio 2022 +- Visual Studio 2019 - Visual Studio 2017 -- Visual Studio 2015 clone_depth: 50 init: - cmd: >- @@ -23,6 +24,12 @@ init: python -m pip install coverage || VER>NUL + python -m pip install --upgrade doctest || VER>NUL + + python -m pip install pytest || VER>NUL + + python -m pip install pytest-cov || VER>NUL + choco install codecov || VER>NUL dir @@ -30,7 +37,7 @@ build_script: - cmd: make clean test_script: - cmd: >- - make test || VER>NUL + make test-pytest || VER>NUL dir @@ -42,7 +49,7 @@ test_script: dir - codecov || VER>NUL + codecov -f coverage.xml || VER>NUL dir diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b4b66c8..ddf5fbaa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,17 +2,17 @@ version: 2 jobs: build: docker: - - image: circleci/python:3.6.1 - image: circleci/python:3.7 - image: circleci/python:3.8 - - image: circleci/python:3.9 + - image: circleci/python:3.9 + resource_class: medium environment: CI: cicleci DEBIAN_FRONTEND: noninteractive LANG: en_US.UTF-8 - LC_CTYPE: en_EN.UTF-8 + LC_CTYPE: en_US.UTF-8 SHELL: /bin/bash - working_directory: ~/python-repo + working_directory: ~/multicast steps: - checkout - run: @@ -29,31 +29,31 @@ jobs: shell: /bin/bash name: "install coverage attempt" command: | - python3 -m pip install --user coverage || true + python3 -m pip install --upgrade --user coverage || true - run: name: "install flake8 attempt" command: | - python3 -m pip install --user flake8 || true + python3 -m pip install --upgrade --user flake8 || true when: on_success - save_cache: key: v1-repo-{{ .Environment.CIRCLE_SHA1 }} paths: - - ~/python-repo + - ~/multicast test: docker: - - image: circleci/python:3.6.1 - image: circleci/python:3.7 - image: circleci/python:3.8 - - image: circleci/python:3.9 + - image: circleci/python:3.9 parallelism: 2 + resource_class: medium environment: CI: cicleci DEBIAN_FRONTEND: noninteractive LANG: en_US.UTF-8 - LC_CTYPE: en_EN.UTF-8 + LC_CTYPE: en_US.UTF-8 SHELL: /bin/bash - working_directory: ~/python-repo + working_directory: ~/multicast steps: - restore_cache: key: v1-repo-{{ .Environment.CIRCLE_SHA1 }} @@ -67,7 +67,7 @@ jobs: shell: /bin/bash name: "Installing newer deps" command: | - python3 -m pip install --user coverage3 || true + python3 -m pip install --upgrade --user coverage3 || true when: on_fail - run: shell: /bin/bash @@ -96,26 +96,32 @@ jobs: pytest: docker: - - image: circleci/python:3.6.1 - image: circleci/python:3.7 - image: circleci/python:3.8 - - image: circleci/python:3.9 + - image: circleci/python:3.9 parallelism: 2 + resource_class: medium environment: CI: cicleci DEBIAN_FRONTEND: noninteractive LANG: en_US.UTF-8 - LC_CTYPE: en_EN.UTF-8 + LC_CTYPE: en_US.UTF-8 SHELL: /bin/bash - working_directory: ~/python-repo + working_directory: ~/multicast steps: - restore_cache: key: v1-repo-{{ .Environment.CIRCLE_SHA1 }} + - run: + shell: /bin/bash + name: "setup for doctest" + command: | + python3 -m pip install --upgrade --user doctest || true + when: on_success - run: shell: /bin/bash name: "clean up for test" command: | - python3 -m pip install --user coverage || true + python3 -m pip install --upgrade --user coverage || true when: on_success - run: shell: /bin/bash @@ -123,6 +129,12 @@ jobs: command: | python3 -m pip install --user pytest || true when: on_success + - run: + shell: /bin/bash + name: "setup up for pytest" + command: | + python3 -m pip install --user pytest-cov || true + when: on_success - run: shell: /bin/bash name: "clean up for pytest" @@ -157,13 +169,14 @@ jobs: lint: docker: - image: circleci/python:3.9 + resource_class: medium environment: CI: cicleci DEBIAN_FRONTEND: noninteractive LANG: en_US.UTF-8 SHELL: /bin/bash - LC_CTYPE: en_EN.UTF-8 - working_directory: ~/python-repo + LC_CTYPE: en_US.UTF-8 + working_directory: ~/multicast steps: - restore_cache: key: v1-repo-{{ .Environment.CIRCLE_SHA1 }} @@ -201,3 +214,4 @@ workflows: - pytest: requires: - build + - test diff --git a/.codecov.yml b/.codecov.yml index 2e91d53b..299bd363 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,17 +1,68 @@ +codecov: + max_report_age: off + + coverage: + precision: 2 + range: "80...100" status: + default_rules: + flag_coverage_not_uploaded_behavior: exclude project: - default: on - target: 95% + default: + target: auto base: auto - tests: + branches: + - master + - stageing + - stable + if_ci_failed: error #success, failure, error, ignore + only_pulls: false + multicast: + target: 95% + threshold: 1% + flags: + - multicast + paths: + - "multicast/" + - "setup.py" + - "!tests/" + unittests: target: 80% - flags: tests + threshold: 5% + flags: + - unittests paths: - - tests + - "tests/" + -flags: - tests: - paths: - - tests - joined: false +flag_management: + default_rules: # the rules that will be followed for any flag added, generally + carryforward: false + statuses: + - name_prefix: project- + type: project + target: auto + threshold: 0% + - name_prefix: patch- + type: patch + target: 50% + individual_flags: # exceptions to the default rules above, stated flag by flag + - name: multicast #fill in your own flag name + paths: + - multicast/*.py #fill in your own path. Note, accepts globs, not regexes + - setup.py + carryforward: true + statuses: + - type: project + target: 90% + - name: tests + paths: + - tests/check_* #fill in your own path. Note, accepts globs, not regexes + - tests/*.py #fill in your own path. Note, accepts globs, not regexes + - "!multicast/" + carryforward: true + - name: unittests + paths: + - tests/ #fill in your own path. Note, accepts globs, not regexes + carryforward: true \ No newline at end of file diff --git a/.coveragerc b/.coveragerc index 335f068a..e4b35673 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,24 +1,50 @@ [run] parallel = True +branch = True +concurrency = multiprocessing [report] include = multicast*,tests* # Regexes for lines to exclude from consideration exclude_lines = - # Have to re-enable the standard pragma - pragma: no cover - except Exception - except BaseException: - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - raise ImportError - except unittest.SkipTest - except IOError - except OSError + # Have to re-enable the standard pragma + pragma: no cover + from . import + except Exception + except BaseException: + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + raise ImportError + except subprocess.CalledProcessError + except ..Error + # don't complain about sys.modules + sys.modules + not in sys.modules: + # Don't complain if non-runnable code isn't run: + if __name__ in u'__main__': + if __name__ .. .__main__.: + if __sys_path__ not in sys.path: + os.abort() + exit - # Don't complain if non-runnable code isn't run: - if __name__ in '__main__': + +partial_branches = + # Have to re-enable the standard pragma rules + pragma: no branch + finally: + except unittest.SkipTest + # Don't complain if non-runnable code isn't run: + if __name__ in u'__main__': + if __name__ in '__main__': + if __sys_path__ not in sys.path: + # don't complain about sys.modules + sys.modules + not in sys.modules: + if context.__name__ is None: + if 'os' not in sys.modules: + if 'os.path' not in sys.modules: + if 'argparse' not in sys.modules: ignore_errors = True diff --git a/.gitattributes b/.gitattributes index ed62237c..1640b879 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,15 +2,18 @@ *.cfg text diff=config *.txt text *.py text working-tree-encoding=UTF-8 diff=python +multicast/*.py text working-tree-encoding=UTF-8 diff=python +tests/*.py text working-tree-encoding=UTF-8 diff=python +*.pyc -text *.sh text -*.bash text +*.bash text diff=shell *.ini text -*.md text +*.md text working-tree-encoding=UTF-8 *.yml text *.jpg -text *.png -text -*.conf text +*.conf text diff=config *.bat text *.rst text -tests/check_* text +tests/check_* text working-tree-encoding=UTF-8 diff=shell Makefile text diff=makefile diff --git a/.github/workflows/Labeler.yml b/.github/workflows/Labeler.yml new file mode 100644 index 00000000..331ddc9b --- /dev/null +++ b/.github/workflows/Labeler.yml @@ -0,0 +1,11 @@ +name: "Pull Request Labeler" +on: +- pull_request_target + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v3 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index ac35d01a..d04dc62a 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -1,49 +1,236 @@ name: CI - on: [push] - jobs: - MATS: + BUILD: + runs-on: ubuntu-latest + env: + LANG: "en_US.UTF-8" + steps: + - uses: actions/checkout@master + - name: python + uses: actions/setup-python@master + with: + python-version: 3.9 + - name: Pre-Clean + id: clean + run: make -j1 -f Makefile purge 2>/dev/null || true ; + - name: Test Build + id: build + run: make -j1 -f Makefile build ; + - name: Post-Clean + id: post + run: make -j1 -f Makefile purge || true ; + BOOTSTRAP: + if: ${{ always() }} + needs: BUILD runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.7, 3.8, 3.9] + lang-var: ["en_US.UTF-8", "de.UTF-8", "jp.UTF-8"] + env: + PYTHON_VERSION: ${{ matrix.python-version }} + LANG: ${{ matrix.lang-var }} + steps: + - uses: actions/checkout@master + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Setup dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + pip install -r ./requirements.txt + - name: Pre-build + id: bootstrap + run: | + make -j1 -f Makefile clean || true ; + make -j1 -f Makefile build ; + - name: Run Tests + id: test-install + run: make -j1 -f Makefile user-install ; + - name: Test Info + id: test-info + run: python -m setup.py --name --version --license || true ; + - name: Post-Clean + id: post + run: | + make -j1 -f Makefile purge || true ; + make -j1 -f Makefile clean || true ; + if: ${{ always() }} + + MATS: + if: ${{ always() }} + needs: BUILD + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.7, 3.8, 3.9] + env: + PYTHON_VERSION: ${{ matrix.python-version }} + LANG: "en_US.UTF-8" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@master + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Setup dependencies + run: | + pip install -r ./requirements.txt ; + pip install coverage || true ; - name: Pre-Clean + id: clean run: make -j1 -f Makefile clean || true ; - name: Run Tests + id: tests + run: make -j1 -f Makefile test ; + - name: Post-Clean + id: post + run: make -j1 -f Makefile clean || true ; + if: ${{ always() }} + + COVERAGE: + if: ${{ success() }} + needs: [BUILD, MATS] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.7, 3.8, 3.9] + env: + OS: ${{ matrix.os }} + PYTHON_VERSION: ${{ matrix.python-version }} + LANG: "en_US.UTF-8" + CODECLIMATE_REPO_TOKEN: ${{ secrets.CODECLIMATE_TOKEN }} + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + steps: + - uses: actions/checkout@master + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies for ${{ matrix.python-version }} + run: | + python -m pip install --upgrade pip setuptools wheel + pip install -r ./requirements.txt ; + pip install coverage ; + pip install pytest ; + pip install pytest-cov + pip install coverage ; + - name: Install code-climate tools for ${{ matrix.python-version }} + if: ${{ runner.os }} == "Linux" + shell: bash run: | - echo Testing M.A.T.s, - make -j1 -f Makefile test || true + if [ $OS == ubuntu-latest ] ; then curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter || true ; fi ; + if [ $OS == ubuntu-latest ] ; then chmod +x ./cc-test-reporter 2>/dev/null || true ; fi + if [ $OS == ubuntu-latest ] ; then ./cc-test-reporter before-build || true ; fi + - name: Install deepsource tools for ${{ matrix.python-version }} + if: ${{ runner.os }} == "Linux" + shell: bash + run: | + if [ $OS == ubuntu-latest ] ; then (curl https://deepsource.io/cli | sh) || true ; else echo "SKIP deepsource" ; fi ; + - name: Pre-Clean + id: clean + run: make -j1 -f Makefile clean || true ; + - name: Generate Coverage for py${{ matrix.python-version }} + run: make -j1 -f Makefile test-pytest ; + - name: Upload Python ${{ matrix.python-version }} coverage to Codecov + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + directory: . + flags: ${{ matrix.os }},${{ matrix.python-version }} + name: multicast-github-${{ matrix.os }}-${{ matrix.python-version }} + verbose: true + fail_ci_if_error: true + - name: Upload Python ${{ matrix.python-version }} Artifact + uses: actions/upload-artifact@v3 + with: + name: Test-Report-${{ matrix.os }}-${{ matrix.python-version }} + path: ./test-reports/ + if-no-files-found: ignore + - name: code-climate for ${{ matrix.python-version }} + if: ${{ runner.os }} == "Linux" + shell: bash + run: | + if [ $OS == ubuntu-latest ] ; then ./cc-test-reporter after-build --exit-code 0 || true ; else echo "SKIP code climate" ; fi ; + - name: deepsource for ${{ matrix.python-version }} + if: ${{ runner.os }} == "Linux" + shell: bash + run: | + if [ $OS == ubuntu-latest ] ; then ./bin/deepsource report --analyzer test-coverage --key python --value-file ./coverage.xml 2>/dev/null || true ; else echo "SKIP deepsource" ; fi ; - name: Post-Clean + id: post run: make -j1 -f Makefile clean || true ; + if: ${{ always() }} - STYLE: + STYLE: + if: ${{ always() }} + needs: [BUILD, MATS] runs-on: ubuntu-latest + env: + PYTHON_VERSION: '3.9' + LANG: "en_US.UTF-8" + steps: - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@master + with: + python-version: 3.9 + - name: Setup Linters + run: | + python -m pip install --upgrade pip setuptools wheel + pip install -r ./requirements.txt ; + pip install flake8 ; + pip install pyflakes ; + pip install pep8 ; - name: Pre-Clean + id: clean run: make -j1 -f Makefile clean || true ; - - name: Run Tests - run: | - echo Testing Style, - make -j1 -f Makefile test-style || true + - name: Testing Style + id: style + run: make -j1 -f Makefile test-style ; - name: Post-Clean + id: post run: make -j1 -f Makefile clean || true ; + if: ${{ always() }} - BUILD: + TOX: + if: ${{ success() }} + needs: [MATS, STYLE, COVERAGE] runs-on: ubuntu-latest + env: + PYTHON_VERSION: '3.9' + steps: - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@master + with: + python-version: 3.9 + - name: install TOX + run: | + python -m pip install --upgrade pip setuptools wheel tox + pip install -r ./requirements.txt ; + pip install coverage || true ; + pip install flake8 || true ; + pip install pyflakes || true ; + pip install pep8 || true ; - name: Pre-Clean + id: clean run: make -j1 -f Makefile clean || true ; - - name: Run Tests - run: | - echo Testing Build, - make -j1 -f Makefile build || true + - name: Testing Tox + run: make -j1 -f Makefile test-tox || true ; - name: Post-Clean - run: make -j1 -f Makefile purge || true ; + id: post + run: make -j1 -f Makefile clean || true ; + if: ${{ always() }} diff --git a/.github/workflows/labler.yml b/.github/workflows/labler.yml deleted file mode 100644 index 47716d46..00000000 --- a/.github/workflows/labler.yml +++ /dev/null @@ -1,17 +0,0 @@ -# This workflow will triage pull requests and apply a label based on the -# paths that are modified in the pull request. -# -# To use this workflow, you will need to set up a .github/labeler.yml -# file with configuration. For more information, see: -# https://github.com/actions/labeler/blob/master/README.md - -name: "Pull Request Labeler" -on: [pull_request] - -jobs: - label: - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v2 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5d7e4e3b..05d9f344 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ # Byte-compiled / optimized / DLL files __pycache__/ +./*/__pycache__/* +./*/__pycache__/ *.py[cod] *$py.class *~ +*/*~ +./**/*~ # C extensions *.so @@ -38,6 +42,8 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +test-results/ +test-results/* .coverage .coverage.* .cache @@ -91,10 +97,12 @@ ENV/ # Rope project settings .ropeproject - -#Mac OSX files +# Mac OSX files .DS_Store */.DS_Store +.*/*/.DS_Store *.xcworkspace *.xcworkspace/* +*.xcodeproj +*.xcodeproj/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0835f615..00000000 --- a/.travis.yml +++ /dev/null @@ -1,278 +0,0 @@ -language: python - -python: - -dist: - -addons: - coverity_scan: - # GitHub project metadata - project: - name: reactive-firewall/multicast - version: 1.1.3 - description: Python Multicast Repo - # Commands to prepare for build_command - # ** likely specific to your build ** - build_command_prepend: make clean - # The command that will be added as an argument to "cov-build" to compile your project for analysis, - # ** likely specific to your build ** - build_command: make test - # Pattern to match selecting branches that will run analysis. We recommend leaving this set to 'coverity_scan'. - # Take care in resource usage, and consider the build frequency allowances per - # https://scan.coverity.com/faq#frequency - branch_pattern: master - -branches: - only: - - stable - - master - -env: - -matrix: - include: - - os: linux - python: "2.7" - env: TRAVIS_PYTHON_VERSION="2.7" - dist: trusty - - os: linux - python: "3.2" - env: TRAVIS_PYTHON_VERSION="3.2" - dist: trusty - - os: linux - python: "3.3" - env: TRAVIS_PYTHON_VERSION="3.3" - dist: trusty - - os: linux - python: "3.4" - dist: xenial - env: TRAVIS_PYTHON_VERSION="3.4" - - os: linux - python: "3.5" - env: TRAVIS_PYTHON_VERSION="3.5" - dist: trusty - - os: linux - python: "3.5" - env: TRAVIS_PYTHON_VERSION="3.5" - dist: xenial - - os: linux - python: "3.6" - env: TRAVIS_PYTHON_VERSION="3.6" - - os: linux - python: "3.5-dev" # 3.5 development branch - env: TRAVIS_PYTHON_VERSION="3.5" - dist: xenial - - os: linux - python: "3.6-dev" - env: TRAVIS_PYTHON_VERSION="3.6" - dist: bionic - - os: linux - python: "3.7-dev" - env: TRAVIS_PYTHON_VERSION="3.7" - dist: bionic - - os: linux - python: "3.7" - env: TRAVIS_PYTHON_VERSION="3.7" - dist: bionic - - os: linux - python: "3.8-dev" - env: TRAVIS_PYTHON_VERSION="3.8" - dist: bionic - - os: linux - python: "3.9-dev" - env: TRAVIS_PYTHON_VERSION="3.9" - dist: bionic - - os: linux - python: "pypy2.7-5.8.0" - dist: xenial - - os: linux - python: "pypy3.5-5.8.0" - dist: xenial - - os: linux - python: "pypy3.5-6.0" - dist: xenial - - os: linux - python: "pypy3.5-5.10.0" - env: TRAVIS_PYTHON_VERSION="3.5" - dist: xenial - - os: linux - python: "pypy2.7-5.10.0" - env: TRAVIS_PYTHON_VERSION="2.7" - dist: xenial - - os: linux - python: "nightly" # currently points to 3.9-dev - env: TRAVIS_PYTHON_VERSION="3.9-dev" - - os: osx - osx_image: xcode10 - language: shell - - os: osx - osx_image: xcode11.1 - language: shell - - os: osx - osx_image: xcode11.2 - language: shell - - os: osx - osx_image: xcode11.3 - language: shell - allow_failures: - - os: linux - dist: xenial - - os: linux - dist: trusty - - os: linux - python: "3.9-dev" - - os: linux - python: "2.6" - - os: linux - python: "3.2" - - os: linux - python: "3.3" - - os: linux - python: "3.4" - dist: xenial - - os: linux - python: "3.4" - dist: bionic - - os: linux - python: "3.7-dev" - - os: linux - python: "3.8-dev" - - os: linux - python: "3.9-dev" - - os: linux - python: "nightly" - - os: linux - python: "pypy" - - os: linux - python: "pypy3" - - os: linux - python: "pypy3.5-5.8.0" - - os: linux - python: "pypy2.7-5.8.0" - - os: linux - python: "pypy3.5-5.10.0" - - os: linux - python: "pypy2.7-5.10.0" - - os: linux - python: "pypy3.5-6.0" - - os: osx - osx_image: xcode6.4 - language: shell - - os: osx - osx_image: xcode7.2 - language: shell - - os: osx - osx_image: xcode7.3 - language: shell - - os: osx - osx_image: xcode8 - language: shell - - os: osx - osx_image: xcode8.3 - language: shell - - os: osx - osx_image: xcode9 - language: shell - - os: osx - osx_image: xcode9.2 - language: shell - - os: osx - osx_image: xcode9.3 - language: shell - - os: osx - osx_image: xcode9.4 - language: shell - - os: osx - osx_image: xcode10 - language: shell - - os: osx - osx_image: xcode10.1 - language: shell - - os: osx - osx_image: xcode11 - language: shell - - os: osx - osx_image: xcode11.1 - language: shell - - -install: "make init" - -before_install: - - if [ $TRAVIS_OS_NAME == osx ] ; then travis_wait git -C "$(brew --repo homebrew/core)" fetch --unshallow || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then travis_retry brew tap homebrew/versions || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then travis_wait brew upgrade || travis_retry brew upgrade || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python2.6 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python26 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then travis_wait brew install python3 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python3.3 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install py3.3 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python3.4 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python34 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install py3.4 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python3.5 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python35 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install py3.5 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python3.6 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python36 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install py3.6 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python2.7 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python27 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install py2.7 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python3.6 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install python3.7 || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install coverage $INSTALL || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install codecov || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then brew install pip || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then export VERSIONER_PYTHON_VERSION=${TRAVIS_PYTHON_VERSION:-3.7} || true ; fi - - if [ $TRAVIS_OS_NAME == osx ] ; then defaults write com.apple.versioner.python Version $TRAVIS_PYTHON_VERSION || true ; fi - - travis_retry pip install tox || pip install tox || true ; - - travis_retry pip install coverage || true ; - - travis_retry pip install codecov || true ; - - travis_retry python -m pip install coverage || python -m pip install coverage || true ; - - travis_retry python -m pip install codecov || python -m pip install codecov || true ; - - travis_retry python3 -m pip3 install tox || python3 -m pip install tox || true ; - - travis_retry python3 -m pip3 install coverage || python3 -m pip install coverage || true ; - - travis_retry python3 -m pip3 install codecov || python3 -m pip install codecov || true ; - - travis_retry python3 -m pip3 install -r requirements.txt || python3 -m pip install -r requirements.txt || true ; - -# The following is used to get coveralls working: add affter codecov -# - travis_retry pip install python-coveralls 2>/dev/null || python3 -m pip install python-coveralls || true ; -# - coveralls 2>/dev/null || true -# - travis_retry pip uninstall -y python-coveralls || travis_retry python3 -m pip uninstall -y python-coveralls || true -# - travis_retry pip uninstall -y PyYAML || travis_retry python3 -m pip uninstall -y PyYAML || true - -before_script: - - if [ $TRAVIS_OS_NAME == osx ] ; then echo "SKIP code climate" ; else curl -L --url https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 -o ./cc-test-reporter 3>/dev/null 2>/dev/null || true ; fi ; - - if [ $TRAVIS_OS_NAME == osx ] ; then echo "SKIP code climate" ; else chmod +x ./cc-test-reporter || true ; fi ; - - if [ $TRAVIS_OS_NAME == osx ] ; then echo "SKIP code climate" ; else ./cc-test-reporter before-build || true ; fi ; - - if [ $TRAVIS_OS_NAME == osx ] ; then echo "SKIP deepsource" ; else (curl https://deepsource.io/cli | sh) || true ; fi ; - -script: - - make clean ; - - if [ $TRAVIS_OS_NAME == osx ] ; then echo "SKIP make test" ; else make test || exit $? ; fi ; - - if [ $TRAVIS_OS_NAME == osx ] || [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP codecov" ; else codecov || exit $? ; fi ; - - cp -vf .coverage ".coverall.Lasting.45678.12345" 2>/dev/null || true - - make clean || exit $? ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] || [ $TRAVIS_PYTHON_VERSION == "3.3" ] ; then echo "SKIP make test-tox" ; else make test-tox || exit $? ; fi ; - - make clean || true ; - - mv -vf ".coverall.Lasting.45678.12345" .coverage 2>/dev/null || true - -after_failure: - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP coverage" ; else coverage combine 2>/dev/null || true ; fi ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP coverage xml" ; else coverage xml 2>/dev/null || true ; fi ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP codecov" ; else codecov 2>/dev/null || true ; fi ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP deepsource" ; else ./bin/deepsource report --analyzer test-coverage --key python --value-file ./coverage.xml 2>/dev/null || true ; fi ; - - if [ $TRAVIS_OS_NAME == osx ] || [ $TRAVIS_PYTHON_VERSION == "3.2" ] || [ $TRAVIS_PYTHON_VERSION == "3.3" ] ; then echo "SKIP code climate" ; else ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT || true ; fi ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] || [ $TRAVIS_PYTHON_VERSION == "3.3" ] ; then echo "SKIP codecov" ; else codecov 2>/dev/null || true ; fi ; - - make clean 2>/dev/null || true - -after_success: - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP coverage" ; else coverage combine 2>/dev/null || true ; fi ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP coverage xml" ; else coverage xml 2>/dev/null || true ; fi ; - - if [ $TRAVIS_OS_NAME == osx ] || [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP code climate" ; else ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT || true ; fi ; - - if [ $TRAVIS_OS_NAME == osx ] || [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP deepsource" ; else ./bin/deepsource report --analyzer test-coverage --key python --value-file ./coverage.xml 2>/dev/null || true ; fi ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP codecov" ; else codecov || true ; fi ; - - travis_retry python3 -m pip install python-coveralls || python3 -m pip install python-coveralls || true ; - - if [ $TRAVIS_PYTHON_VERSION == "3.2" ] ; then echo "SKIP codecov" ; else coveralls 2>/dev/null || true ; fi ; - diff --git a/.yamllint.conf b/.yamllint.conf new file mode 100644 index 00000000..6d6934bd --- /dev/null +++ b/.yamllint.conf @@ -0,0 +1,9 @@ + +extends: default + +rules: + line-length: + max: 100 + level: warning + indentation: + indent-sequences: whatever \ No newline at end of file diff --git a/Makefile b/Makefile index 1fe2a830..86be3501 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +ifeq "$(LC_CTYPE)" "" + LC_CTYPE="en_US.UTF-8" +endif ifeq "$(ECHO)" "" ECHO=echo @@ -26,20 +29,15 @@ ifeq "$(LINK)" "" endif ifeq "$(MAKE)" "" - MAKE=make + MAKE=make -j1 endif ifeq "$(PYTHON)" "" - PYTHON=export LC_CTYPE="en_US.utf-8" ; python3 -B + PYTHON=`command -v python3` -B endif ifeq "$(COVERAGE)" "" - ifeq "$(PYTHON)" "" - COVERAGE=$(command -v coverage) - endif - ifeq "$(COVERAGE)" "" - COVERAGE=$(command -v coverage3) - endif + COVERAGE=$(PYTHON) -m coverage endif ifeq "$(WAIT)" "" @@ -68,99 +66,130 @@ ifeq "$(DO_FAIL)" "" DO_FAIL=$(ECHO) "ok" endif +ifeq "$(RM)" "" + RM=`command -v rm` -f +endif + +ifeq "$(RMDIR)" "" + RMDIR=$(RM) -Rd +endif + PHONY: must_be_root cleanup build: - $(QUIET)$(ECHO) "INFO: No need to build. Try make -f Makefile install" - $(QUIET)$(PYTHON) ./setup.py build + $(QUIET)$(ECHO) "INFO: No need to build. Try 'make -f Makefile install'" + $(QUIET)$(PYTHON) setup.py build + $(QUIET)$(PYTHON) setup.py bdist_wheel --universal + $(QUITE)$(WAIT) $(QUIET)$(ECHO) "build DONE." init: + $(QUIET)$(PYTHON) -m pip install --upgrade pip setuptools wheel 2>/dev/null || true $(QUIET)$(ECHO) "$@: Done." -install: must_be_root - $(QUIET)$(PYTHON) -m pip install "git+https://github.com/reactive-firewall/multicast.git#egg=multicast" +install: init build must_be_root + $(QUIET)$(PYTHON) -m pip install -e "git+https://github.com/reactive-firewall/multicast.git#egg=multicast" $(QUITE)$(WAIT) $(QUIET)$(ECHO) "$@: Done." uninstall: - $(QUITE)$(PYTHON) -m pip uninstall multicast || true + $(QUIET)$(PYTHON) -m pip uninstall multicast && python -m pip uninstall multicast 2>/dev/null || true $(QUITE)$(WAIT) $(QUIET)$(ECHO) "$@: Done." -test-reports: - $(QUIET)mkdir test-reports 2>/dev/null >/dev/null || true ; - $(QUIET)$(ECHO) "$@: Done." - purge: clean uninstall - $(QUIET)$(PYTHON) -m pip uninstall multicast && python -m pip uninstall multicast || true - $(QUIET)rm -Rfd ./build/ 2>/dev/null || true - $(QUIET)rm -Rfd ./.eggs/ 2>/dev/null || true + $(QUIET)$(PYTHON) ./setup.py uninstall 2>/dev/null || true + $(QUIET)$(PYTHON) ./setup.py clean || true + $(QUIET)$(RMDIR) ./build/ 2>/dev/null || true + $(QUIET)$(RMDIR) ./dist/ 2>/dev/null || true + $(QUIET)$(RMDIR) ./.eggs/ 2>/dev/null || true + $(QUIET)$(RM) ./test-results/junit.xml 2>/dev/null || true + $(QUIET)$(RMDIR) ./test-reports/ 2>/dev/null || true $(QUIET)$(ECHO) "$@: Done." test: cleanup - $(QUIET)$(COVERAGE) run -p --source=multicast -m unittest discover --verbose -s ./tests -t ./ || $(PYTHON) -m unittest discover --verbose -s ./tests -t ./ || python -m unittest discover --verbose -s ./tests -t ./ || DO_FAIL=exit 2 ; - $(QUIET)$(COVERAGE) combine 2>/dev/null || true - $(QUIET)$(COVERAGE) report --include=multicast* 2>/dev/null || true - $(QUIET)$(DO_FAIL); + $(QUIET)$(COVERAGE) run -p --source=multicast -m unittest discover --verbose --buffer -s ./tests -t ./ || $(PYTHON) -m unittest discover --verbose --buffer -s ./tests -t ./ || DO_FAIL="exit 2" ; + $(QUIET)$(COVERAGE) combine 2>/dev/null || true ; + $(QUIET)$(COVERAGE) report --include=multicast* 2>/dev/null || true ; + $(QUIET)$(DO_FAIL) ; $(QUIET)$(ECHO) "$@: Done." test-tox: cleanup $(QUIET)tox -v -- || tail -n 500 .tox/py*/log/py*.log 2>/dev/null $(QUIET)$(ECHO) "$@: Done." +test-reports: + $(QUIET)mkdir test-reports 2>/dev/null >/dev/null || true ; + $(QUIET)$(ECHO) "$@: Done." + test-pytest: cleanup test-reports - $(QUIET)$(PYTHON) -m pytest --junitxml=test-reports/junit.xml -v tests || python -m pytest --junitxml=test-reports/junit.xml -v tests + $(QUIET)$(PYTHON) -m pytest --cache-clear --doctest-glob=**/*.py --doctest-modules --cov=./ --cov-report=xml --junitxml=test-reports/junit.xml -v --rootdir=. || python -m pytest --doctest-glob=**/*.py --doctest-modules --cov=./ --cov-report=xml --junitxml=test-reports/junit.xml -v . ; wait ; $(QUIET)$(ECHO) "$@: Done." test-style: cleanup - $(QUIET)flake8 --ignore=W191,W391 --max-line-length=100 --verbose --count --config=.flake8.ini + $(QUIET)$(PYTHON) -m flake8 --ignore=W191,W391 --max-line-length=100 --verbose --count --config=.flake8.ini || true $(QUIET)tests/check_spelling 2>/dev/null || true - $(QUIET)tests/check_cc_line.bash 2>/dev/null || true + $(QUIET)tests/check_cc_lines 2>/dev/null || true $(QUIET)$(ECHO) "$@: Done." cleanup: - $(QUIET)rm -f tests/*.pyc 2>/dev/null || true - $(QUIET)rm -f tests/*~ 2>/dev/null || true - $(QUIET)rm -Rfd tests/__pycache__ 2>/dev/null || true - $(QUIET)rm -f multicast/*.pyc 2>/dev/null || true - $(QUIET)rm -Rfd multicast/__pycache__ 2>/dev/null || true - $(QUIET)rm -Rfd multicast/*/__pycache__ 2>/dev/null || true - $(QUIET)rm -f multicast/*~ 2>/dev/null || true - $(QUIET)rm -f *.pyc 2>/dev/null || true - $(QUIET)rm -f multicast/*/*.pyc 2>/dev/null || true - $(QUIET)rm -f multicast/*/*~ 2>/dev/null || true - $(QUIET)rm -f *.DS_Store 2>/dev/null || true - $(QUIET)rm -f ./.DS_Store 2>/dev/null || true - $(QUIET)rm -Rfd .pytest_cache/ 2>/dev/null || true - $(QUIET)rm -Rfd .eggs 2>/dev/null || true - $(QUIET)rmdir ./test-reports/ 2>/dev/null || true - $(QUIET)rm -f multicast/*.DS_Store 2>/dev/null || true - $(QUIET)rm -f multicast/*/*.DS_Store 2>/dev/null || true - $(QUIET)rm -f multicast/.DS_Store 2>/dev/null || true - $(QUIET)rm -f multicast/*/.DS_Store 2>/dev/null || true - $(QUIET)rm -f tests/.DS_Store 2>/dev/null || true - $(QUIET)rm -f tests/*/.DS_Store 2>/dev/null || true - $(QUIET)rm -f multicast.egg-info/* 2>/dev/null || true - $(QUIET)rmdir multicast.egg-info 2>/dev/null || true - $(QUIET)rm -f ./*/*~ 2>/dev/null || true - $(QUIET)rm -f ./*~ 2>/dev/null || true - $(QUIET)coverage erase 2>/dev/null || true - $(QUIET)rm -f ./.coverage 2>/dev/null || true - $(QUIET)rm -f ./coverage*.xml 2>/dev/null || true - $(QUIET)rm -f ./sitecustomize.py 2>/dev/null || true - $(QUIET)rm -f ./.*~ 2>/dev/null || true - $(QUIET)rm -Rfd ./.tox/ 2>/dev/null || true + $(QUIET)$(RM) tests/*.pyc 2>/dev/null || true + $(QUIET)$(RM) tests/*~ 2>/dev/null || true + $(QUIET)$(RM) tests/__pycache__/* 2>/dev/null || true + $(QUIET)$(RM) __pycache__/* 2>/dev/null || true + $(QUIET)$(RM) multicast/*.pyc 2>/dev/null || true + $(QUIET)$(RM) multicast/*~ 2>/dev/null || true + $(QUIET)$(RM) multicast/__pycache__/* 2>/dev/null || true + $(QUIET)$(RM) multicast/*/*.pyc 2>/dev/null || true + $(QUIET)$(RM) multicast/*/*~ 2>/dev/null || true + $(QUIET)$(RM) multicast/*.DS_Store 2>/dev/null || true + $(QUIET)$(RM) multicast/*/*.DS_Store 2>/dev/null || true + $(QUIET)$(RM) multicast/.DS_Store 2>/dev/null || true + $(QUIET)$(RM) multicast/*/.DS_Store 2>/dev/null || true + $(QUIET)$(RM) tests/.DS_Store 2>/dev/null || true + $(QUIET)$(RM) tests/*/.DS_Store 2>/dev/null || true + $(QUIET)$(RM) multicast.egg-info/* 2>/dev/null || true + $(QUIET)$(RM) ./*.pyc 2>/dev/null || true + $(QUIET)$(RM) ./.coverage 2>/dev/null || true + $(QUIET)$(RM) ./coverage*.xml 2>/dev/null || true + $(QUIET)$(RM) ./sitecustomize.py 2>/dev/null || true + $(QUIET)$(RM) ./.DS_Store 2>/dev/null || true + $(QUIET)$(RM) ./*/.DS_Store 2>/dev/null || true + $(QUIET)$(RM) ./*/*~ 2>/dev/null || true + $(QUIET)$(RM) ./.*/*~ 2>/dev/null || true + $(QUIET)$(RM) ./*~ 2>/dev/null || true + $(QUIET)$(RM) ./.*~ 2>/dev/null || true + $(QUIET)$(RM) ./src/**/* 2>/dev/null || true + $(QUIET)$(RM) ./src/* 2>/dev/null || true + $(QUIET)$(RMDIR) ./src/ 2>/dev/null || true + $(QUIET)$(RMDIR) tests/__pycache__ 2>/dev/null || true + $(QUIET)$(RMDIR) multicast/__pycache__ 2>/dev/null || true + $(QUIET)$(RMDIR) multicast/*/__pycache__ 2>/dev/null || true + $(QUIET)$(RMDIR) ./__pycache__ 2>/dev/null || true + $(QUIET)$(RMDIR) multicast.egg-info 2>/dev/null || true + $(QUIET)$(RMDIR) .pytest_cache/ 2>/dev/null || true + $(QUIET)$(RMDIR) .eggs 2>/dev/null || true + $(QUIET)$(RMDIR) ./test-reports/ 2>/dev/null || true + $(QUIET)$(RMDIR) ./.tox/ 2>/dev/null || true + $(QUIET)$(WAIT) ; clean: cleanup - $(QUIET)rm -f test-results/junit.xml 2>/dev/null || true - $(QUIET)rm -Rfd ./build/ 2>/dev/null || true + $(QUIET)$(COVERAGE) erase 2>/dev/null || true + $(QUIET)$(RM) ./test-results/junit.xml 2>/dev/null || true $(QUIET)$(MAKE) -s -C ./docs/ -f Makefile clean 2>/dev/null || true $(QUIET)$(ECHO) "$@: Done." must_be_root: $(QUIET)runner=`whoami` ; \ - if test $$runner != "root" ; then echo "You are not root." ; exit 1 ; fi + if test $$runner != "root" ; then $(ECHO) "You are not root." ; exit 1 ; fi + +user-install: build + $(QUIET)$(PYTHON) -m pip install --user --upgrade pip setuptools wheel || true + $(QUIET)$(PYTHON) -m pip install --user -r "https://github.com/raw/reactive-firewall/multicast/master/requirements.txt" + $(QUIET)$(PYTHON) -m pip install --user -e "git+https://github.com/reactive-firewall/multicast.git#egg=multicast" + $(QUITE)$(WAIT) + $(QUIET)$(ECHO) "$@: Done." + %: $(QUIET)$(ECHO) "No Rule Found For $@" ; $(WAIT) ; diff --git a/README.md b/README.md index d81f3a54..51d98ef3 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,41 @@ -# About +# Multicast Python Repo + +## About + This repo is basically a wrapper for sending and reciveing UDP multicast messages via python. Y.M.M.V. This library is not intended to fully implement the complexeties of multicast traffic, rather to allow a user friendly API for python components to send and recieve accross a multicast transmission. The obvious advantage of this wrapper over unicast solotions is the ability to have multiple nodes comunicate concurently without individual conections for each node pair. -# CI (WIP): +## CI: -Continuous integration testing is handeled by Travis CI Service and Circle-CI Service. +Continuous integration testing is handeled by github actions and the generous Circle-CI Service. ## Status ### master: -[![Status](https://travis-ci.org/reactive-firewall/multicast.svg?branch=master)](https://travis-ci.org/reactive-firewall/multicast) + [![CircleCI](https://circleci.com/gh/reactive-firewall/multicast/tree/master.svg?style=svg)](https://circleci.com/gh/reactive-firewall/multicast/tree/master) -[![Appveyor](https://ci.appveyor.com/api/projects/status/??????/branch/master?svg=true)](https://ci.appveyor.com/project/reactive-firewall/multicast/branch/master) -[![Test Coverage](https://api.codeclimate.com/v1/badges/?????/test_coverage)](https://codeclimate.com/github/reactive-firewall/multicast/test_coverage) +[![CI](https://github.com/reactive-firewall/multicast/actions/workflows/Tests.yml/badge.svg?branch=master)](https://github.com/reactive-firewall/multicast/actions/workflows/Tests.yml) +[![Appveyor](https://ci.appveyor.com/api/projects/status/0h5vuexyty9lbl81/branch/master?svg=true)](https://ci.appveyor.com/project/reactive-firewall/multicast/branch/master) +[![Test Coverage](https://api.codeclimate.com/v1/badges/8a9422860b6a5b6477b5/test_coverage)](https://codeclimate.com/github/reactive-firewall/multicast/test_coverage) [![Code Coverage](https://codecov.io/gh/reactive-firewall/multicast/branch/master/graph/badge.svg)](https://codecov.io/gh/reactive-firewall/multicast/branch/master/) -[![Coverage Status](https://coveralls.io/repos/github/reactive-firewall/multicast/badge.svg?branch=master)](https://coveralls.io/github/reactive-firewall/multicast?branch=master) -[![Coverity](https://scan.coverity.com/projects/????/badge.svg)](https://scan.coverity.com/projects/reactive-firewall-multicast) -[![Code Climate](https://codeclimate.com/github/reactive-firewall/multicast/badges/gpa.svg)](https://codeclimate.com/github/reactive-firewall/multicast) +[![Code Climate](https://api.codeclimate.com/v1/badges/8a9422860b6a5b6477b5/maintainability)](https://codeclimate.com/github/reactive-firewall/multicast) [![CodeFactor](https://www.codefactor.io/repository/github/reactive-firewall/multicast/badge)](https://www.codefactor.io/repository/github/reactive-firewall/multicast) -[![Codebeat badge](https://codebeat.co/badges/?????)](https://codebeat.co/projects/github-com-reactive-firewall-multicast-master) +[![codebeat badge](https://codebeat.co/badges/721f752f-289d-457e-af90-487a85f16bf1)](https://codebeat.co/projects/github-com-reactive-firewall-multicast-master) ![Size](https://img.shields.io/github/languages/code-size/reactive-firewall/multicast.svg) ![Commits-since](https://img.shields.io/github/commits-since/reactive-firewall/multicast/stable.svg?maxAge=9000) ### Stable: -[![Stable-Status](https://travis-ci.org/reactive-firewall/multicast.svg?branch=stable)](https://travis-ci.org/reactive-firewall/multicast) + [![Stable-CircleCI](https://circleci.com/gh/reactive-firewall/multicast/tree/stable.svg?style=svg)](https://circleci.com/gh/reactive-firewall/multicast/tree/stable) -[![Atable-Appveyor](https://ci.appveyor.com/api/projects/status/????/branch/stable?svg=true)](https://ci.appveyor.com/project/reactive-firewall/multicast/branch/stable) +[![Atable-Appveyor](https://ci.appveyor.com/api/projects/status/0h5vuexyty9lbl81/branch/stable?svg=true)](https://ci.appveyor.com/project/reactive-firewall/multicast/branch/stable) [![stable-code-coverage](https://codecov.io/gh/reactive-firewall/multicast/branch/stable/graph/badge.svg)](https://codecov.io/gh/reactive-firewall/multicast/branch/stable/) -[![Stable Coverage Status](https://coveralls.io/repos/github/reactive-firewall/multicast/badge.svg?branch=stable)](https://coveralls.io/github/reactive-firewall/multicast?branch=stable) -[![Staqble Codebeat Badge](https://codebeat.co/badges/????)](https://codebeat.co/projects/github-com-reactive-firewall-multicast-stable) -# How do I get this running? +## FAQ + +### How do I get this running? (assuming python3 is setup and installed) @@ -47,10 +49,11 @@ python3 -m multicast --help ; ``` #### DONE + if all went well multicast is installed and working -# How do I use this to recive UDP Multicast? +### How do I use this to recive UDP Multicast? (assuming project is setup and installed and you want to listen on 0.0.0.0) @@ -59,7 +62,7 @@ if all went well multicast is installed and working python3 -m multicast HEAR --iface='0.0.0.0' --join-mcast-group 224.1.1.2 --bind-group '224.1.1.2' --port 5353 ``` -# How do I use this to send UDP Multicast? +### How do I use this to send UDP Multicast? (assuming project is setup and installed) @@ -68,9 +71,9 @@ python3 -m multicast HEAR --iface='0.0.0.0' --join-mcast-group 224.1.1.2 --bind- python3 -m multicast SAY --mcast-group 224.1.1.2 --port 5353 --message "Hello World!" ``` -# Dev Testing: +## Dev Testing: -In a rush? Then use this: +#### In a debug rush? Then use this to test: ```bash make clean ; # cleans up from any previous tests hopefully @@ -78,7 +81,7 @@ make test ; # runs the tests make clean ; # cleans up for next test ``` -Use PEP8 to check code style? Great! Try this: +#### Use PEP8 to check python code style? Great! Try this: ```bash make clean ; # cleans up from any previous tests hopefully @@ -86,7 +89,7 @@ make test-style ; # runs the tests make clean ; # cleans up for next test ``` -Want more tests? Cool! Try `tox`: +#### Want more tests? Cool! Try `tox`: ```bash make clean ; # cleans up from any previous tests hopefully @@ -94,11 +97,13 @@ make test-tox ; # runs the tox tests make clean ; # cleans up for next test ``` -# Next steps: +## Next steps: +(UNSTABLE) clean up Proof-of-concept code into a recognizable python module project. +(WIP) might expand the documentation to be more user friendly to the non-network guru (WIP) might add tcp multicast ... who knows? - #### Copyright (c) 2021-2022, Mr. Walls + [![License - MIT](https://img.shields.io/github/license/reactive-firewall/multicast.svg?maxAge=3600)](https://github.com/reactive-firewall/multicast/blob/stable/LICENSE.md) diff --git a/multicast/.gitattributes b/multicast/.gitattributes deleted file mode 120000 index 6b504345..00000000 --- a/multicast/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -../.gitattributes \ No newline at end of file diff --git a/multicast/__init__.py b/multicast/__init__.py index b7e73fec..93e555f2 100644 --- a/multicast/__init__.py +++ b/multicast/__init__.py @@ -16,60 +16,89 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Contains the Python Multicast library.""" + __all__ = [ - """__package__""", """__module__""", """__name__""", """__version__""", """__prolog__""", - """__doc__""", """__MCAST_DEFAULT_PORT""", """recv""", """send""" + """__package__""", """__module__""", """__name__""", """__version__""", """__prologue__""", + """__doc__""", """__BLANK""", """__MCAST_DEFAULT_PORT""", """__MCAST_DEFAULT_GROUP""", + """__MCAST_DEFAULT_TTL""", """recv""", """send""" ] __package__ = """multicast""" +"""The package of this program. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast as _multicast + >>> + + >>> _multicast.__package__ is not None + True + >>> + +""" __module__ = """multicast""" +"""The module of this program. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast as _multicast + >>> + + >>> _multicast.__module__ is not None + True + >>> + +""" __name__ = """multicast""" +"""The name of this program. + Minimal Acceptance Testing: -global __version__ -"""The version of this program.""" + First setup test fixtures by importing multicast. -__version__ = """1.2.0""" + >>> import multicast as _multicast + >>> + >>> _multicast.__name__ is not None + True + >>> -global __MCAST_DEFAULT_PORT """ - Arbitrary port to use by default, though any dynamic and free port would work - Testing: - First setup test fixtures by importing multicast. +global __version__ - >>> import multicast - >>> +__version__ = """1.3.0""" +"""The version of this program. - >>> multicast.recv is not None - True - >>> + Minimal Acceptance Testing: - Testcase 0: Multicast should have a default port. - A: Test that the __MCAST_DEFAULT_PORT attribute is initialized. - B: Test that the __MCAST_DEFAULT_PORT attribute is an int. + First setup test fixtures by importing multicast. - >>> multicast.__MCAST_DEFAULT_PORT is not None - True - >>> type(multicast.__MCAST_DEFAULT_PORT) is type(1) - True - >>> + >>> import multicast as _multicast + >>> -""" + >>> _multicast.__version__ is not None + True + >>> -__MCAST_DEFAULT_PORT = 19991 +""" -__prolog__ = str("""Python Multicast library version {version}.""").format(version=__version__) +__prologue__ = str("""Python Multicast library version {version}.""").format(version=__version__) +"""The one-line description or summary of this program.""" -__doc__ = __prolog__ + """ +__doc__ = __prologue__ + """ Minimal Acceptance Testing: @@ -86,7 +115,7 @@ True >>> - Testcase 0: multicast.recv should have a doctests. + Testcase 0: multicast.recv should have a doctests. >>> import multicast.recv >>> @@ -95,7 +124,135 @@ True >>> - """ + Testcase 1: multicast.send should have a doctests. + + >>> import multicast.send + >>> + + >>> multicast.send.__module__ is not None + True + >>> + + Testcase 2: multicast.__main__ should have a doctests. + + >>> import multicast.__main__ as _main + >>> + + >>> _main.__module__ is not None + True + >>> _main.__doc__ is not None + True + >>> + + +""" + + +global __MCAST_DEFAULT_PORT # noqa + +__MCAST_DEFAULT_PORT = 19991 +""" + Arbitrary port to use by default, though any dynamic and free port would work. + + Minimal Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> + + Testcase 0: Multicast should have a default port. + A: Test that the __MCAST_DEFAULT_PORT attribute is initialized. + B: Test that the __MCAST_DEFAULT_PORT attribute is an int. + + >>> multicast.__MCAST_DEFAULT_PORT is not None + True + >>> type(multicast.__MCAST_DEFAULT_PORT) is type(1) + True + >>> + >>> multicast.__MCAST_DEFAULT_PORT > int(1024) + True + >>> + +""" + +global __MCAST_DEFAULT_GROUP # noqa + +__MCAST_DEFAULT_GROUP = """224.0.0.1""" +""" + Arbitrary group to use by default, though any mcst grp would work. + + Minimal Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> + + Testcase 0: Multicast should have a default port. + A: Test that the __MCAST_DEFAULT_GROUP attribute is initialized. + B: Test that the __MCAST_DEFAULT_GROUP attribute is an IP string. + + >>> multicast.__MCAST_DEFAULT_GROUP is not None + True + >>> type(multicast.__MCAST_DEFAULT_GROUP) is type(str) + True + >>> + +""" + + +global __MCAST_DEFAULT_TTL # noqa +""" + Arbitrary TTL time to live to use by default, though any small (2-126) TTL would work. + + Minimal Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> + + Testcase 0: Multicast should have a default TTL. + A: Test that the __MCAST_DEFAULT_TTL attribute is initialized. + B: Test that the __MCAST_DEFAULT_TTL attribute is an int. + + >>> multicast.__MCAST_DEFAULT_TTL is not None + True + >>> type(multicast.__MCAST_DEFAULT_TTL) is type(1) + True + >>> + +""" + + +global __BLANK # noqa + +__BLANK = str("""""") +""" + Arbitrary blank string. + + Minimal Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> __BLANK = multicast.__BLANK + + Testcase 0: Multicast should have a default port. + A: Test that the __BLANK attribute is initialized. + B: Test that the __BLANK attribute is an empty string. + + >>> __BLANK is not None + True + >>> type(__BLANK) is type(str) + True + >>> + >>> len(__BLANK) <= 0 + True + >>> + +""" try: @@ -114,10 +271,22 @@ raise ImportError(err) +try: + import unicodedata + if unicodedata.__name__ is None: + raise ImportError("FAIL: we could not import unicodedata. ABORT.") +except Exception as err: + raise ImportError(err) + + try: import socket if socket.__name__ is None: raise ImportError("FAIL: we could not import socket. ABORT.") + if sys.platform.startswith("darwin") or sys.platform.startswith("linux"): # pragma: no-branch + __MCAST_DEFAULT_TTL = int(socket.IP_DEFAULT_MULTICAST_TTL) # pragma: no cover + else: # pragma: no-branch + __MCAST_DEFAULT_TTL = int(20) # pragma: no cover except Exception as err: raise ImportError(err) @@ -147,21 +316,18 @@ send = sys.modules["""multicast.send"""] except Exception as importErr: del importErr - import multicast.recv as send + import multicast.send as send -if __name__ in '__main__': - try: - if 'multicast.__main__' not in sys.modules: - from . import __main__ as __main__ - else: # pragma: no branch - __main__ = sys.modules["""multicast.__main__"""] - except Exception: - from . import __main__ as __main__ +try: + if """multicast.__main__""" in sys.modules: # pragma: no cover + __main__ = sys.modules["""multicast.__main__"""] +except Exception: + import multicast.__main__ as __main__ + -if __name__ in '__main__': +if __name__ in u'__main__': __EXIT_CODE = 2 - if __main__.__name__ is None: - raise ImportError(str("Failed to open multicast")) - __EXIT_CODE = __main__.main(sys.argv[1:]) + if __main__.main is not None: + __EXIT_CODE = __main__.main(sys.argv[1:]) exit(__EXIT_CODE) diff --git a/multicast/__main__.py b/multicast/__main__.py index 0abbb5e7..56dc8c7e 100644 --- a/multicast/__main__.py +++ b/multicast/__main__.py @@ -17,49 +17,62 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""The Main Entrypoint.""" + + +__all__ = [ + """__package__""", """__module__""", """__name__""", """__proc__""", + """__prologue__""", """__epilogue__""", """__doc__""", """__checkToolArgs""", + """NoOp""", """SendMCast""", """joinMCast""", """dumpUsage""", + """buildArgs""", """main""" +] + + __package__ = """multicast""" -__module__ = """multicast""" +__module__ = """multicast.__main__""" __file__ = """multicast/__main__.py""" -__prog__ = str("""multicast""") -"""The name of this program is Python Multicast""" +# __name__ = """multicast.__main__""" + + +__proc__ = """multicast""" -__description__ = str( - """Add a Description Here""" -) -"""Contains the description of the program.""" +__prologue__ = """The Main Entrypoint.""" -__epilog__ = str( - """Add an epilog here.""" -) -"""Contains the short epilog of the program CLI help text.""" +__epilogue__ = """Add an epilogue here.""" -__doc__ = __description__ + """ +__doc__ = __prologue__ + """ Minimal Acceptance Testing: First setup test fixtures by importing multicast. - >>> import multicast + >>> import multicast as multicast + >>> + >>> import multicast.__main__ >>> >>> multicast.__doc__ is not None True >>> + >>> multicast.__main__.__doc__ is not None + True + >>> + >>> multicast.__version__ is not None True >>> - Testcase 0: multicast.__main__ should have a doctests. + Testcase 0: multicast.__main__ should have a doctests. >>> import multicast.__main__ >>> @@ -68,7 +81,13 @@ True >>> - """ + >>> multicast.__main__.__doc__ is not None + True + >>> + + +""" + try: import sys @@ -83,7 +102,7 @@ err = None del(err) # Throw more relevant Error - raise ImportError(str("Error Importing Python")) + raise ImportError(str("[CWE-440] Error Importing Python")) try: @@ -117,24 +136,26 @@ def NoOp(*args, **kwargs): - """The meaning of Nothing. This function should be self-explanitory; + """Do Nothing. + + The meaning of Nothing. This function should be self-explanitory; it does 'no operation' i.e. nothing. - + Minimal Acceptance Testing: First setup test fixtures by importing multicast. - >>> import multicast + >>> import multicast.__main__ >>> - Testcase 0: multicast.__main__ should have a doctests. + Testcase 0: multicast.__main__ should have a doctests. >>> import multicast.__main__ >>> multicast.__main__.__module__ is not None True >>> - Testcase 1: multicast.NoOp should return None. + Testcase 1: multicast.NoOp should return None. >>> import multicast.__main__ >>> multicast.__main__.NoOp() is None @@ -144,22 +165,23 @@ def NoOp(*args, **kwargs): >>> """ - return None + return None # noqa def SendMCast(*args, **kwargs): - """Sends a multicast message""" + """Will Send a multicast message.""" return send.main(*args, **kwargs) def joinMCast(*args, **kwargs): - """recv multicast messages""" + """Will listen for multicast messages.""" return recv.main(*args, **kwargs) def dumpUsage(*args, **kwargs): - """Prints help usage""" - return buildArgs().print_help() + """Will prints help usage.""" + buildArgs().print_help() + return None # noqa # More boiler-plate-code @@ -175,13 +197,39 @@ def dumpUsage(*args, **kwargs): def buildArgs(): - """Utility Function to build argparse parser. + """Will build the argparse parser. + + Utility Function to build the argparse parser; see argparse.ArgumentParser for more. returns argparse.ArgumentParser - the ArgumentParser to use. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> import multicast.__main__ + >>> multicast.__main__ is not None + True + >>> + + Testcase 0: buildArgs should return an ArgumentParser. + A: Test that the multicast.__main__ component is initialized. + B: Test that the recv.buildArgs component is initialized. + + >>> multicast.__main__ is not None + True + >>> multicast.__main__.buildArgs is not None + True + >>> type(multicast.__main__.buildArgs()) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + <...ArgumentParser...> + >>> + + """ parser = argparse.ArgumentParser( - prog=__prog__, - description=__description__, - epilog=__epilog__, + prog=__proc__, + description=__prologue__, + epilog=__epilogue__, add_help=False ) group = parser.add_mutually_exclusive_group(required=False) @@ -194,66 +242,136 @@ def buildArgs(): ) parser.add_argument( 'some_task', nargs='?', choices=TASK_OPTIONS.keys(), - help='the help text for this option.' + help='the action and any action arguments to pass.' ) return parser def parseArgs(arguments=None): - """Parses the CLI arguments. See argparse.ArgumentParser for more. + """Will attempt to parse the given CLI arguments. + + See argparse.ArgumentParser for more. param str - arguments - the array of arguments to parse. Usually sys.argv[1:] returns argparse.Namespace - the Namespace parsed with the key-value pairs. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> import multicast.__main__ + >>> multicast.__main__ is not None + True + >>> + + Testcase 0: parseArgs should return a namespace. + A: Test that the multicast.__main__ component is initialized. + B: Test that the __main__ component is initialized. + C: Test that the __main__.parseArgs component is initialized. + + >>> multicast.__main__ is not None + True + >>> multicast.__main__.parseArgs is not None + True + >>> tst_fxtr_args = ['''NOOP''', '''--port=1234''', '''--iface=127.0.0.1'''] + >>> test_fixture = multicast.__main__.parseArgs(tst_fxtr_args) + >>> test_fixture is not None + True + >>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + <...Namespace...> + >>> + + """ arguments = __checkToolArgs(arguments) return buildArgs().parse_known_intermixed_args(arguments) def __checkToolArgs(args): - """Handles None case for arguments as a helper function.""" - if args is None: - args = [None] - return args + """Will handle the None case for arguments. + + Used as a helper function. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> + + Testcase 0: multicast.__main__ should have a doctests. + + >>> import multicast.__main__ + >>> multicast.__main__.__module__ is not None + True + >>> + + Testcase 1: multicast.__checkToolArgs should return an array. + + >>> import multicast.__main__ + >>> multicast.__main__.__checkToolArgs(None) is not None + True + >>> type(multicast.__main__.__checkToolArgs(None)) is type([None]) + True + >>> + + Testcase 2: multicast.__checkToolArgs should return an array. + + >>> import multicast.__main__ + >>> type(multicast.__main__.__checkToolArgs(["arg1", "arg2"])) is type(["strings"]) + True + >>> type(multicast.__main__.__checkToolArgs([0, 42])) is type([int(1)]) + True + >>> + + + """ + return [None] if args is None else args def useTool(tool, *arguments): - """Handler for launching the functions.""" + """Will Handle launching the actual task functions.""" theResult = None arguments = __checkToolArgs(arguments) if (tool is not None) and (tool in TASK_OPTIONS.keys()): try: - # print(str("launching: " + tool)) theResult = TASK_OPTIONS[tool](*arguments) - except Exception: - raise NotImplementedError("""Not Implemented.""") - return theResult + except Exception: # pragma: no branch + raise NotImplementedError("""[CWE-440] Not Implemented.""") + return theResult # noqa def main(*argv): - """The Main Event.""" - __EXIT_CODE = 0 + """Do main event stuff.""" + __EXIT_CODE = 1 try: try: - args, extra = parseArgs(*argv) + (args, extra) = parseArgs(*argv) service_cmd = args.some_task __EXIT_CODE = useTool(service_cmd, extra) - except Exception as inerr: + except Exception as inerr: # pragma: no branch w = str("WARNING - An error occured while") w += str(" handling the arguments.") w += str(" Cascading failure.") - print(w) - print(str(inerr)) - print(str(inerr.args())) + if (sys.stdout.isatty()): # pragma: no cover + print(w) + print(str(inerr)) + print(str(inerr.args())) + del inerr __EXIT_CODE = 2 - except Exception: + except BaseException: # pragma: no branch e = str("CRITICAL - An error occured while handling") e += str(" the cascading failure.") - print(e) + if (sys.stdout.isatty()): # pragma: no cover + print(str(e)) __EXIT_CODE = 3 - return __EXIT_CODE + return __EXIT_CODE # noqa if __name__ in '__main__': __EXIT_CODE = 2 - if (sys.argv is not None) and (len(sys.argv) >= 1): + if (sys.argv is not None) and (len(sys.argv) > 1): __EXIT_CODE = main(sys.argv[1:]) + elif (sys.argv is not None): + __EXIT_CODE = main([str(__proc__), """-h"""]) exit(__EXIT_CODE) diff --git a/multicast/recv.py b/multicast/recv.py index 03ef3e8b..906ddbf1 100644 --- a/multicast/recv.py +++ b/multicast/recv.py @@ -18,7 +18,7 @@ # limitations under the License. -# Third-party Acknowlegement: +# Third-party Acknowledgement: # .......................................... # Some code (namely: run, and parseArgs) was modified/derived from: # https://stackoverflow.com/a/52791404 @@ -30,7 +30,13 @@ # .......................................... # NO ASSOCIATION -__all__ = ["""main""", """run""", """parseArgs""", """__module__""", """__name__""", """__doc__"""] +"""multicast HEAR ...""" + +__all__ = [ + """__package__""", """genSocket""", """endSocket""", """parseArgs""", """hearstep""", """main""", + """__module__""", """__name__""", """__proc__""", """__prologue__""", + """__epilogue__""", """__doc__""" +] __package__ = """multicast""" @@ -48,44 +54,23 @@ __proc__ = """multicast HEAR""" -__doc__ = """Python Multicast Reciver. - - Minimal Acceptance Testing: - - First setup test fixtures by importing multicast. - - Testcase 0: Multicast should be importable. - - >>> import multicast - >>> - - >>> multicast.__doc__ is not None - True - >>> - - Testcase 1: Recv should be automaticly imported. - A: Test that the multicast component is initialized. - B: Test that the recv component is initialized. - - >>> import multicast - >>> +__epilogue__ = """Generally speaking you want to bind to one of the groups you joined in + this module/instance, but it is also possible to bind to group which + is added by some other programs (like another python program instance of this) +""" - >>> multicast is not None - True - >>> multicast.recv is not None - True - >>> +__prologue__ = """Python Multicast Receiver. Spawns a listener for multicast based on arguments.""" -""" try: import sys + import argparse + import unicodedata import socket import struct - import argparse depends = [ - socket, struct, argparse + unicodedata, socket, struct, argparse ] for unit in depends: try: @@ -100,38 +85,178 @@ try: - if 'multicast.__MCAST_DEFAULT_PORT' not in sys.modules: - from . import __MCAST_DEFAULT_PORT as __MCAST_DEFAULT_PORT + if 'multicast' not in sys.modules: + from . import multicast as multicast else: # pragma: no branch - __MCAST_DEFAULT_PORT = sys.modules["""multicast.__MCAST_DEFAULT_PORT"""] + multicast = sys.modules["""multicast"""] + __BLANK = multicast.__BLANK except Exception as importErr: del importErr - import multicast.__MCAST_DEFAULT_PORT as __MCAST_DEFAULT_PORT + import multicast as multicast + + +def genSocket(): + """Will generate an unbound socket.socket object ready to receive network traffic. + + Implementation allows reuse of socket (to allow another instance of python running + this script binding to the same ip/port). + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> multicast.__doc__ is not None + True + >>> + + Testcase 0: Recv should be automaticly imported. + A: Test that the multicast component is initialized. + B: Test that the recv component is initialized. + + >>> multicast is not None + True + >>> multicast.recv is not None + True + >>> + + Testcase 1: Recv should have genSocket() function that returns a socket.socket object. + A: Test that the recv component has the function 'genSocket' + B: Test that the 'genSocket' function returns a socket + + >>> multicast.recv.genSocket is not None + True + >>> multicast.recv.genSocket #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + + >>> type(multicast.recv.genSocket) + + >>> type(multicast.recv.genSocket()) + + >>> + + + """ + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.settimeout(multicast.__MCAST_DEFAULT_TTL) + return sock + + +def endSocket(sock=None): + """Will generates an unbound socket.socket object ready to receive network traffic. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> multicast.__doc__ is not None + True + >>> + + Testcase 0: Recv should be automaticly imported. + A: Test that the multicast component is initialized. + B: Test that the recv component is initialized. + + >>> multicast is not None + True + >>> multicast.recv is not None + True + >>> + + Testcase 1: Recv should have endSocket() function that takes a socket.socket and closes it. + A: Test that the recv component has the function 'genSocket' + B: Test that the recv component has the function 'endSocket' + C: Test that the 'endSocket' function returns None when given the genSocket + + >>> multicast.recv.genSocket is not None + True + >>> multicast.recv.endSocket is not None + True + >>> multicast.recv.endSocket #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + + >>> type(multicast.recv.endSocket) + + >>> temp_fxtr = multicast.recv.endSocket(multicast.recv.genSocket()) + >>> temp_fxtr is None + True + >>> + + Testcase 2: Recv should have endSocket() function that takes a socket.socket object, + otherwise does nothing. + A: Test that the recv component has the function 'endSocket' (see testcase 1) + B: Test that the 'endSocket' function returns nothing + + >>> multicast.recv.endSocket is not None + True + >>> multicast.recv.endSocket #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + + >>> type(multicast.recv.endSocket) + + >>> multicast.recv.endSocket(None) is None + True + >>> + + + """ + if not (sock is None): # pragma: no branch + try: + sock.close() + sock.shutdown(socket.SHUT_RD) # pragma: no cover + except OSError: # pragma: no branch + sock = None def parseArgs(arguments=None): - """Parses the CLI arguments. See argparse.ArgumentParser for more. + """Will attempt to parse the given CLI arguments. + + See argparse.ArgumentParser for more. param str - arguments - the array of arguments to parse. Usually sys.argv[1:] returns argparse.Namespace - the Namespace parsed with the key-value pairs. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> multicast.recv is not None + True + >>> + + Testcase 0: parseArgs should return a namespace. + A: Test that the multicast component is initialized. + B: Test that the recv component is initialized. + C: Test that the recv.parseArgs component is initialized. + + >>> multicast.recv is not None + True + >>> multicast.recv.parseArgs is not None + True + >>> tst_fxtr_args = ['''--port=1234''', '''--iface=127.0.0.1'''] + >>> test_fixture = multicast.recv.parseArgs(tst_fxtr_args) + >>> test_fixture is not None + True + >>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + <...Namespace...> + >>> + + """ - __epilog__ = """- WIP -""" - __description__ = """Python Multicast Reciver.""" parser = argparse.ArgumentParser( prog=__proc__, - description=__description__, - epilog=__epilog__, - exit_on_error=False + description=__prologue__, + epilog=__epilogue__ ) - parser.add_argument("""--port""", type=int, default=__MCAST_DEFAULT_PORT) + parser.add_argument("""--port""", type=int, default=multicast.__MCAST_DEFAULT_PORT) parser.add_argument( - '--join-mcast-groups', default=[], nargs='*', - help="""multicast groups (ip addrs) to listen to join""" + """--join-mcast-groups""", default=[], nargs='*', + help="""multicast groups (ip addrs) to listen to join.""" ) + __tmp_help = """local interface to use for listening to multicast data; """ + __tmp_help += """if unspecified, any one interface may be chosen.""" parser.add_argument( - '--iface', default=None, - help=str("""local interface to use for listening to multicast data; """).join( - """if unspecified, any one interface may be chosen""" - ) + """--iface""", default=None, + help=str(__tmp_help) ) __tmp_help = """multicast groups (ip addrs) to bind to for the udp socket; """ __tmp_help += """should be one of the multicast groups joined globally """ @@ -140,27 +265,55 @@ def parseArgs(arguments=None): __tmp_help += """If unspecified, bind to 0.0.0.0 """ __tmp_help += """(all addresses (all multicast addresses) of that interface)""" parser.add_argument( - '--bind-group', default=None, + """--bind-group""", default=None, help=str(__tmp_help) ) return parser.parse_args(arguments) -def run(groups, port, iface=None, bind_group=None): - """The work-horse function. Spawns a listener for multicast based on arguments. - generally speaking you want to bind to one of the groups you joined in - this module/instance, but it is also possible to bind to group which - is added by some other programs (like another python program instance of this) - """ - - # assert bind_group in groups + [None], \ - # 'bind group not in groups to join' - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) +def hearstep(groups, port, iface=None, bind_group=None): + """Will listen on the given port of an interface for multicast messages to the given group(s). - # allow reuse of socket (to allow another instance of python running this - # script binding to the same ip/port) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - + The work-horse function. + + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> multicast.recv is not None + True + >>> + + Testcase 1: Stability testing. + + >>> import multicast + >>> + >>> multicast.recv is None + False + >>> + >>> multicast.recv.hearstep is None + False + >>> type(multicast.recv.hearstep) + + >>> multicast.recv.hearstep(None, 19991) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + '...' + >>> tst_fxtr = multicast.__MCAST_DEFAULT_GROUP + >>> multicast.recv.hearstep([tst_fxtr], 19991) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + '...' + >>> multicast.recv.hearstep( + ... [tst_fxtr], 19991, None, tst_fxtr + ... ) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + '...' + >>> + + + """ + if groups is None: + groups = [] + sock = genSocket() + msgbuffer = str(__BLANK) try: sock.bind(('' if bind_group is None else bind_group, port)) for group in groups: @@ -170,46 +323,163 @@ def run(groups, port, iface=None, bind_group=None): socket.INADDR_ANY if iface is None else socket.inet_aton(iface) ) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) - + chunk = None while True: - print(sock.recv(1316)) + chunk = sock.recv(1316) + if not (chunk is None): + msgbuffer += str(chunk, encoding='utf8') + chunk = None + # msgbuffer += unicodedata.lookup("""SOFT HYPHEN""") # about 969 bytes in base64 encoded as chars - except KeyboardInterrupt: - print("") - print(str("""Closing""")) + except KeyboardInterrupt: # pragma: no branch + if (sys.stdout.isatty()): # pragma: no cover + print(__BLANK) + print(str("""User Interrupted""")) + except OSError: # pragma: no branch + if (sys.stdout.isatty()): # pragma: no cover + print(__BLANK) finally: - try: - sock.shutdown(socket.SHUT_RD) - sock.close() - except OSError: - False - sock = None + sock = endSocket(sock) + return msgbuffer def main(*argv): - """The Main Event. This does two things: + """Will handle the Main Event from multicast.__main__ when called. + + This does two things: 1: calls parseArgs() and passes the given arguments, handling any errors if needed. - 2: calls run with the parsed args if able and handles any errors regardles - + 2: calls hearstep with the parsed args if able and handles any errors regardles + + Every main(*args) function in multicast is expected to return an int(). Regardles of errors the result as an 'exit code' (int) is returned. - (Note the __main__ handler just exits with this code as a true return code status.) + The only exception is multicast.__main__.main(*args) which will exit with the underlying + return codes. + The expected return codes are as follows: + = 0: Any nominal state (i.e. no errors and possibly success) + <=1: Any erroneous state (caveat: includes simple failure) + = 2: Any failed state + = 3: Any undefined (but assumed erroneous) state + > 0: implicitly erroneous and treated same as abs(exit_code) would be. + + param iterable - argv - the array of arguments. Usually sys.argv[1:] + returns int - the Namespace parsed with the key-value pairs. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> multicast.send is not None + True + >>> + + Testcase 0: main should return an int. + A: Test that the multicast component is initialized. + B: Test that the send component is initialized. + C: Test that the send.main function is initialized. + D: Test that the send.main function returns an int 0-3. + + >>> multicast.send is not None + True + >>> multicast.send.main is not None + True + >>> tst_fxtr_args = ['''--port=1234''', '''--message''', '''is required'''] + >>> test_fixture = multicast.send.main(tst_fxtr_args) + >>> test_fixture is not None + True + >>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + <...int...> + >>> int(test_fixture) >= int(0) + True + >>> type(test_fixture) is type(0) + True + >>> int(test_fixture) < int(4) + True + >>> (int(test_fixture) >= int(0)) and (int(test_fixture) < int(4)) + True + >>> + + """ - __exit_code = 0 + __exit_code = 1 try: args = parseArgs(*argv) - run(args.join_mcast_groups, int(args.port), args.iface, args.bind_group) - except argparse.ArgumentError: - print('Input has an Argument Error') + response = hearstep(args.join_mcast_groups, int(args.port), args.iface, args.bind_group) + if (sys.stdout.isatty() and len(response) > 0): # pragma: no cover + print(__BLANK) + print(str(response)) + print(__BLANK) + del response + __exit_code = 0 + except argparse.ArgumentError: # pragma: no branch + if (sys.stdout.isatty()): # pragma: no cover + print(__BLANK) + print(str("""Input has an Argument Error""")) __exit_code = 2 except Exception as e: - print(str(e)) + if (sys.stdout.isatty()): # pragma: no cover + print(str(e)) __exit_code = 3 - return __exit_code + return int(__exit_code) + + +__doc__ = str( + __prologue__ + """ + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. -if __name__ == '__main__': - __exit_code = 2 - if (sys.argv is not None) and (len(sys.argv) >= 1): - __exit_code = main(sys.argv[1:]) - exit(__exit_code) + Testcase 0: Multicast should be importable. + + >>> import multicast + >>> + >>> multicast.__doc__ is not None + True + >>> + + Testcase 1: Recv should be automaticly imported. + A: Test that the multicast component is initialized. + B: Test that the recv component is initialized. + C: Test that the recv component has __doc__ + + >>> multicast is not None + True + >>> multicast.recv is not None + True + >>> multicast.recv.__doc__ is not None + True + >>> type(multicast.recv.__doc__) == type(str('''''')) + True + >>> + + Testcase 2: Recv should be detailed with some metadata. + A: Test that the __MAGIC__ variables are initialized. + B: Test that the __MAGIC__ variables are strings. + + >>> multicast.recv is not None + True + >>> multicast.recv.__module__ is not None + True + >>> multicast.recv.__package__ is not None + True + >>> type(multicast.recv.__doc__) == type(multicast.recv.__module__) + True + >>> multicast.recv.__prologue__ is not None + True + >>> type(multicast.recv.__doc__) == type(multicast.recv.__prologue__) + True + >>> multicast.recv.__epilogue__ is not None + True + >>> type(multicast.recv.__doc__) == type(multicast.recv.__epilogue__) + True + >>> type(multicast.recv.__doc__) == type(multicast.recv.__proc__) + True + >>> + + +""" + __epilogue__ + str( + genSocket.__doc__ + endSocket.__doc__ + parseArgs.__doc__ + hearstep.__doc__ + ) + main.__doc__ +) diff --git a/multicast/send.py b/multicast/send.py index d3c7a3d7..0863848e 100644 --- a/multicast/send.py +++ b/multicast/send.py @@ -9,7 +9,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # .......................................... -# http://www.github.com/reactive-firewall/python-repo/LICENSE.md +# https://www.github.com/reactive-firewall/multicast/LICENSE # .......................................... # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -18,7 +18,7 @@ # limitations under the License. -# Third-party Acknowlegement: +# Third-party Acknowledgement: # .......................................... # Some code (namely: run, and parseArgs) was modified/derived from: # https://stackoverflow.com/a/52791404 @@ -30,25 +30,43 @@ # .......................................... # NO ASSOCIATION -__all__ = ["""main""", """run""", """parseArgs""", """__module__""", """__name__""", """__doc__"""] +"""Python Multicast Broadcaster. +Minimal Acceptance Testing: -__package__ = """multicast""" + First setup test fixtures by importing multicast. + Testcase 0: Multicast should be importable. -__module__ = """multicast""" + >>> import multicast + >>> + Testcase 1: Send should be automaticly imported. + A: Test that the send component is initialized. + B: Test that the send.__MAGIC__ components are initialized. -__file__ = """multicast/send.py""" + >>> multicast.send is not None + True + >>> + >>> multicast.send.__doc__ is not None + True + >>> -__name__ = """multicast.send""" + >>> multicast.send.__module__ is not None + True + >>> + + >>> multicast.send.__proc__ is not None + True + >>> -__proc__ = "multicast SAY" +""" -__doc__ = """Python Multicast Broadcaster. +__package__ = """multicast""" +"""The package of this program. Minimal Acceptance Testing: @@ -56,36 +74,75 @@ Testcase 0: Multicast should be importable. - >>> import multicast - >>> + >>> import multicast + >>> - >>> multicast.__doc__ is not None - True - >>> + Testcase 1: Send should be automaticly imported. - Testcase 1: Recv should be automaticly imported. - A: Test that the __main__ component is initialized. - B: Test that the send component is initialized. + >>> multicast.send.__package__ is not None + True + >>> + +""" + + +__module__ = """multicast""" +"""The module of this program. + + Minimal Acceptance Testing: - >>> import multicast - >>> + First setup test fixtures by importing multicast. + + Testcase 0: Multicast should be importable. - >>> multicast.__main__ is not None - True - >>> multicast.send is not None - True - >>> + >>> import multicast + >>> + Testcase 1: Send should be automaticly imported. + + >>> multicast.send.__module__ is not None + True + >>> """ +__file__ = """multicast/send.py""" +"""The file of this component.""" + + +__name__ = """multicast.send""" +"""The name of this component. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + Testcase 0: Multicast should be importable. + + >>> import multicast + >>> + + Testcase 1: Send should be automaticly imported. + + >>> multicast.send.__name__ is not None + True + >>> + +""" + + +__proc__ = """multicast SAY""" +"""The name of this program.""" + + try: import sys - import socket import argparse + import unicodedata + import socket depends = [ - socket, argparse + unicodedata, socket, argparse ] for unit in depends: try: @@ -100,62 +157,151 @@ try: - if 'multicast.__MCAST_DEFAULT_PORT' not in sys.modules: - from . import __MCAST_DEFAULT_PORT as __MCAST_DEFAULT_PORT + if 'multicast' not in sys.modules: + from . import multicast as multicast else: # pragma: no branch - __MCAST_DEFAULT_PORT = sys.modules["""multicast.__MCAST_DEFAULT_PORT"""] + multicast = sys.modules["""multicast"""] + __BLANK = multicast.__BLANK except Exception as importErr: del importErr - import multicast.__MCAST_DEFAULT_PORT as __MCAST_DEFAULT_PORT + import multicast as multicast def parseArgs(*arguments): - """Parses the CLI arguments. See argparse.ArgumentParser for more. - param str - arguments - the array of arguments to parse. Usually sys.argv[1:] + """Will attempt to parse the given CLI arguments. + + See argparse.ArgumentParser for more. + param str - arguments - the array of arguments to parse. (Usually sys.argv[1:]) returns argparse.Namespace - the Namespace parsed with the key-value pairs. + + Testing: + + Testcase 0: First setup test fixtures by importing multicast. + + >>> import multicast + >>> multicast.send is not None + True + >>> + + Testcase 1: parseArgs should return a namespace. + A: Test that the multicast component is initialized. + B: Test that the send component is initialized. + C: Test that the send.parseArgs component is initialized. + + >>> multicast.send is not None + True + >>> multicast.send.parseArgs is not None + True + >>> tst_fxtr_args = ['''--port=1234''', '''--message''', '''is required'''] + >>> test_fixture = multicast.send.parseArgs(tst_fxtr_args) + >>> test_fixture is not None + True + >>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + <...Namespace...> + >>> + + """ - __epilog__ = """- WIP -""" + __epilogue__ = """- WIP -""" __description__ = """Python Multicast Broadcaster.""" parser = argparse.ArgumentParser( prog=__proc__, description=__description__, - epilog=__epilog__, - exit_on_error=False + epilog=__epilogue__ ) - parser.add_argument("""--port""", type=int, default=__MCAST_DEFAULT_PORT) - parser.add_argument('--mcast-group', default='224.1.1.1') + parser.add_argument("""--port""", type=int, default=multicast.__MCAST_DEFAULT_PORT) + parser.add_argument("""--mcast-group""", default=multicast.__MCAST_DEFAULT_GROUP) parser.add_argument( - """--message""", dest="""message""", - default=str("""PING from multicast_send.py: group: {group}, port: {port}""") + """--message""", nargs='+', dest="""message""", + default=str("""PING from {name}: group: {group}, port: {port}""") ) return parser.parse_args(*arguments) -def run(group, port, data): - MULTICAST_TTL = 20 +def saystep(group, port, data): + """Will send the given data over the given port to the given group. + + The actual magic is handeled here. + """ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, MULTICAST_TTL) - sock.sendto(data.encode('utf8'), (group, port)) + try: + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, multicast.__MCAST_DEFAULT_TTL) + sock.sendto(data.encode('utf8'), (group, port)) + finally: + try: + sock.close() + except OSError: # pragma: no branch + sock = None def main(*argv): - """The Main Event.""" + """Will handle the Main Event from multicast.__main__ when called. + + Every main(*args) function in multicast is expected to return an int(). + Regardles of errors the result as an 'exit code' (int) is returned. + The only exception is multicast.__main__.main(*args) which will exit with the underlying + return codes. + The expected return codes are as follows: + = 0: Any nominal state (i.e. no errors and possibly success) + <=1: Any erroneous state (caveat: includes simple failure) + = 2: Any failed state + = 3: Any undefined (but assumed erroneous) state + > 0: implicitly erroneous and treated same as abs(exit_code) would be. + + param iterable - argv - the array of arguments. Usually sys.argv[1:] + returns int - the Namespace parsed with the key-value pairs. + + Minimal Acceptance Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> multicast.send is not None + True + >>> + + Testcase 0: main should return an int. + A: Test that the multicast component is initialized. + B: Test that the send component is initialized. + C: Test that the send.main function is initialized. + D: Test that the send.main function returns an int 0-3. + + >>> multicast.send is not None + True + >>> multicast.send.main is not None + True + >>> tst_fxtr_args = ['''--port=1234''', '''--message''', '''is required'''] + >>> test_fixture = multicast.send.main(tst_fxtr_args) + >>> test_fixture is not None + True + >>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + <...int...> + >>> int(test_fixture) >= int(0) + True + >>> int(test_fixture) < int(4) + True + >>> + + + """ __exit_code = 1 try: args = parseArgs(*argv) - run(args.mcast_group, int(args.port), args.message) + _payload = str(args.message).format( + name=str(__name__), + group=str(args.mcast_group), + port=int(args.port) + ) + saystep(args.mcast_group, int(args.port), _payload) __exit_code = 0 - except argparse.ArgumentError: - print('Input has an Argument Error') + except argparse.ArgumentError: # pragma: no branch + if (sys.stdout.isatty()): # pragma: no cover + print(__BLANK) + print(str("""Input has an Argument Error""")) __exit_code = 2 - except Exception as e: - print(str(e)) + except Exception as e: # pragma: no branch + if (sys.stdout.isatty()): # pragma: no cover + print(str(e)) __exit_code = 3 - return __exit_code - - -if __name__ == '__main__': - __exit_code = 2 - if (sys.argv is not None) and (len(sys.argv) >= 1): - __exit_code = main(sys.argv[1:]) - exit(__exit_code) + del e + return int(__exit_code) diff --git a/requirements.txt b/requirements.txt index aabf1729..150b7498 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,16 +23,16 @@ # struct - builtin - PSF licence argparse>=1.4.0 # argparse - builtin - PSF licence -setuptools>=38.0 +setuptools>=45.0 # virtualenv - MIT #virtualenv>=15.0.1 # six - MIT #six>=1.0.0 # pgpy - BSD 3-Clause licensed #pgpy>=0.4.1 -tox>=3.0.0 +#tox>=3.0.0 #py>=1.4.33 -pip>=19.0 +pip>=21.0 # multicast - MIT #-e git+https://github.com/reactive-firewall/multicast.git#egg=multicast diff --git a/setup.cfg b/setup.cfg index 544bf3ad..45756cca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,44 +1,49 @@ [metadata] name = multicast -version = 1.2.0 +version = 1.3.0 author = Mr. Walls author_email = reactive-firewall@users.noreply.github.com -summary = Python multicast repo for send/recv stubs -description_file = file: README.md +summary = Python Multicast Repo for Send/Recv Stubs. +description_file = README.md long_description = file: README.md long_description_content_type = text/markdown home_page = https://github.com/reactive-firewall/multicast download_url = https://github.com/reactive-firewall/multicast.git -license = file: LICENSE.md -classifiers = - License :: OSI Approved :: MIT License - Development Status :: 4 - Beta - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.5 +license = MIT +license_file = LICENSE.md +license_files = + LICENSE[.md]* + +platform = any +project_urls = + Bug Tracker = https://github.com/reactive-firewall/multicast/issues [bdist_rpm] -url = https://www.github.com/reactive-firewall/multicast.git +url = https://github.com/reactive-firewall/multicast.git [bdist_wheel] universal=1 -[files] -packages = multicast - [options] -py_modules = piaplib -python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* -packages = find:*.py +zip_safe = false +py_modules = multicast +test_suite = tests +python_requires = >=3.7, !=3.11.* +setup_requires = + setuptools>=45.0.0 + wheel>=0.37.0 + scripts = multicast/__main__.py +packages = find: + [options.packages.find] +where = + multicast/ + tests/ + *.py +include = + multicast exclude = tests - diff --git a/setup.py b/setup.py index 7add32d6..03216139 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # .......................................... -# http://www.github.com/reactive-firewall/multicast/LICENSE.md +# http://www.github.com/reactive-firewall/multicast/LICENSE # .......................................... # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -17,15 +17,49 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Sets up the package. + +Minimal Acceptance Testing: + + Testcase 0: Just setup test fixtures by importing multicast. + + >>> import multicast + >>> + >>> multicast.__package__ is not None + True + >>> + +""" + try: from setuptools import setup from setuptools import find_packages + from setuptools.config import read_configuration except Exception: - raise NotImplementedError("""Not Implemented.""") + raise NotImplementedError("""[CWE-440] Not Implemented.""") def readFile(filename): - """Helper Function to read files""" + """Will attempt to read the file at with the given filename or path. + + Used as a helper function to read files and return strings with the content. + + Testing: + + First setup test fixtures by importing multicast. + + >>> import multicast + >>> + + Testcase 0: Should have Function readFile() WHEN loading setup.py. + + >>> multicast.readFile is not None + True + >>> type(multicast.readFile) is type(1) + False + >>> + + """ theResult = None try: with open(str("""./{}""").format(str(filename))) as f: @@ -34,7 +68,7 @@ def readFile(filename): theResult = str( """See https://github.com/reactive-firewall/multicast/{}""" ).format(filename) - return theResult + return str(theResult) try: @@ -43,18 +77,48 @@ def readFile(filename): except Exception: requirements = None + +conf_dict = read_configuration("""setup.cfg""", ignore_option_errors=True) + + readme = readFile("""README.md""") +"""The multi-line description and/or summary of this program.""" + SLA = readFile("""LICENSE.md""") +"""The "Software License Agreement" of this program.""" + +try: + class_tags = [ + str("""Development Status :: 4 - Beta"""), + str("""Environment :: Console"""), + str("""Intended Audience :: Developers"""), + str("""Operating System :: MacOS :: MacOS X"""), + str("""Operating System :: POSIX :: Linux"""), + str("""License :: OSI Approved :: MIT License"""), + str("""Programming Language :: Python"""), + str("""Programming Language :: Python :: 3.9"""), + str("""Programming Language :: Python :: 3.8"""), + str("""Programming Language :: Python :: 3.7"""), + str("""Topic :: Software Development :: Libraries :: Python Modules"""), + str("""Topic :: Network""") + ] +except Exception: + class_tags = str("""Development Status :: 4 - Beta""") setup( - name="""multicast""", - version="""1.2.0""", - description="""Python Multicast Repo for Send/Recv Stubs""", + name=conf_dict["""metadata"""]["""name"""], + version=conf_dict["""metadata"""]["""version"""], + description=conf_dict["""metadata"""]["""description"""], long_description=readme, + long_description_content_type="""text/markdown""", + zip_safe=False, + include_package_data=True, install_requires=requirements, - author="""reactive-firewall""", - author_email="""reactive-firewall@users.noreply.github.com""", - url="""https://github.com/reactive-firewall/multicast.git""", + author=conf_dict["""metadata"""]["""author"""], + author_email=conf_dict["""metadata"""]["""author_email"""], + classifiers=class_tags, + url=conf_dict["""metadata"""]["""url"""], + download_url=conf_dict["""metadata"""]["""download_url"""], license=SLA, - packages=find_packages(exclude=("""tests""")), + packages=find_packages(exclude=("""tests""", """docs""")), ) diff --git a/tests/.gitattributes b/tests/.gitattributes deleted file mode 120000 index 6b504345..00000000 --- a/tests/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -../.gitattributes \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index db988670..5e22d5ed 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,8 +17,21 @@ # limitations under the License. -__module__ = """tests""" +"""Multicast Testing Module. + + Testing: + + Testcase 0: Load tests fixtures + + >>> import tests as _tests + >>> _tests.__module__ is not None + True + +""" + + +__module__ = """tests""" try: import sys @@ -105,15 +118,45 @@ exit(0) -from tests import context +try: + if 'tests.context' not in sys.modules: + from tests import context + else: # pragma: no branch + context = sys.modules["""tests.context"""] +except Exception: # pragma: no branch + raise ImportError("[CWE-440] context Failed to import.") + test_cases = ( test_basic.BasicTestSuite, test_usage.MulticastTestSuite, test_usage.BasicIntegrationTestSuite ) + def load_tests(loader, tests, pattern): - import doctest + """Will Load the tests from the project and then attempts to load the doctests too. + + Testing: + + Testcase 0: Load test fixtures + + >>> import tests as _tests + >>> + + Testcase 1: Load test fixtures + + >>> import tests as _tests + >>> _tests.load_tests is not None + True + + """ + try: + if 'doctest' not in sys.modules: + import doctest + else: # pragma: no branch + doctest = sys.modules["""doctest"""] + except Exception: # pragma: no branch + raise ImportError("[CWE-440] doctest Failed to import.") finder = doctest.DocTestFinder(verbose=True, recurse=True, exclude_empty=True) suite = unittest.TestSuite() for test_class in test_cases: diff --git a/tests/check_cc_line.bash b/tests/check_cc_lines similarity index 86% rename from tests/check_cc_line.bash rename to tests/check_cc_lines index b9c2326a..1dcc6670 100755 --- a/tests/check_cc_line.bash +++ b/tests/check_cc_lines @@ -64,23 +64,31 @@ ulimit -t 600 PATH="/bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" umask 137 -LOCK_FILE="/tmp/cc_line_test_script_lock" +LOCK_FILE="${TMPDIR:-/tmp}/cc_line_test_script_lock" EXIT_CODE=0 test -x $(command -v grep) || exit 126 ; +test -x $(command -v curl) || exit 126 ; +test -x $(command -v find) || exit 126 ; +test -x $(command -v git) || exit 126 ; + +function cleanup() { + rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; +} if [[ ( $(shlock -f ${LOCK_FILE} -p $$ ) -eq 0 ) ]] ; then - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGHUP || EXIT_CODE=3 - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGTERM || EXIT_CODE=4 - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGQUIT || EXIT_CODE=5 - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGSTOP || EXIT_CODE=7 - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGINT || EXIT_CODE=8 - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGABRT || EXIT_CODE=9 - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit ${EXIT_CODE} ;' EXIT || EXIT_CODE=1 + trap 'cleanup ; wait ; exit 1 ;' SIGHUP || EXIT_CODE=3 + trap 'cleanup ; wait ; exit 1 ;' SIGTERM || EXIT_CODE=4 + trap 'cleanup ; wait ; exit 1 ;' SIGQUIT || EXIT_CODE=5 + # SC2173 - https://github.com/koalaman/shellcheck/wiki/SC2173 + # trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGSTOP || EXIT_CODE=7 + trap 'cleanup ; wait ; exit 1 ;' SIGINT || EXIT_CODE=8 + trap 'cleanup ; wait ; exit 1 ;' SIGABRT || EXIT_CODE=9 + trap 'cleanup ; wait ; exit ${EXIT_CODE} ;' EXIT || EXIT_CODE=1 else - echo Test already in progress by `head ${LOCK_FILE}` ; - false ; - exit 126 ; + echo "Check for Copyright lines already in progress by "`head ${LOCK_FILE}` ; + false ; + exit 126 ; fi # this is how test files are found: @@ -88,7 +96,6 @@ fi # THIS IS THE ACTUAL TEST _TEST_ROOT_DIR="./" ; if [[ -d ./multicast ]] ; then - _TEST_ROOT_DIR="./multicast" ; _TEST_ROOT_DIR="./" ; elif [[ -d ./tests ]] ; then _TEST_ROOT_DIR="./" ; @@ -105,7 +112,7 @@ for _TEST_DOC in $(find ${_TEST_ROOT_DIR} \( -iname '*.py' -o -iname '*.txt' -o EXIT_CODE=126 else if [[ ($(grep -cF "Copyright" "${_TEST_DOC}" 2>&1 || EXIT_CODE=$? ;) -le 0) ]] ; then - echo "FAIL: ${_TEST_DOC} is missing a copyright line" ; + echo "FAIL: ${_TEST_DOC} is missing a copyright line" >&2 ; EXIT_CODE=127 fi if [[ ( $(grep -F "Copyright" "${_TEST_DOC}" 2>&1 | grep -coF "Copyright (c)" 2>&1) -le 0) ]] ; then diff --git a/tests/check_codecov b/tests/check_codecov new file mode 100755 index 00000000..0c7d87b9 --- /dev/null +++ b/tests/check_codecov @@ -0,0 +1,176 @@ +#! /bin/bash +# Disclaimer of Warranties. +# A. YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT, TO THE EXTENT PERMITTED BY +# APPLICABLE LAW, USE OF THIS SHELL SCRIPT AND ANY SERVICES PERFORMED +# BY OR ACCESSED THROUGH THIS SHELL SCRIPT IS AT YOUR SOLE RISK AND +# THAT THE ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY AND +# EFFORT IS WITH YOU. +# +# B. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SHELL SCRIPT +# AND SERVICES ARE PROVIDED "AS IS" AND “AS AVAILABLE”, WITH ALL FAULTS AND +# WITHOUT WARRANTY OF ANY KIND, AND THE AUTHOR OF THIS SHELL SCRIPT'S LICENSORS +# (COLLECTIVELY REFERRED TO AS "THE AUTHOR" FOR THE PURPOSES OF THIS DISCLAIMER) +# HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH RESPECT TO THIS SHELL SCRIPT +# SOFTWARE AND SERVICES, EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT +# NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF +# MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE, +# ACCURACY, QUIET ENJOYMENT, AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS. +# +# C. THE AUTHOR DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE +# THE AUTHOR's SOFTWARE AND SERVICES, THAT THE FUNCTIONS CONTAINED IN, OR +# SERVICES PERFORMED OR PROVIDED BY, THIS SHELL SCRIPT WILL MEET YOUR +# REQUIREMENTS, THAT THE OPERATION OF THIS SHELL SCRIPT OR SERVICES WILL +# BE UNINTERRUPTED OR ERROR-FREE, THAT ANY SERVICES WILL CONTINUE TO BE MADE +# AVAILABLE, THAT THIS SHELL SCRIPT OR SERVICES WILL BE COMPATIBLE OR +# WORK WITH ANY THIRD PARTY SOFTWARE, APPLICATIONS OR THIRD PARTY SERVICES, +# OR THAT DEFECTS IN THIS SHELL SCRIPT OR SERVICES WILL BE CORRECTED. +# INSTALLATION OF THIS THE AUTHOR SOFTWARE MAY AFFECT THE USABILITY OF THIRD +# PARTY SOFTWARE, APPLICATIONS OR THIRD PARTY SERVICES. +# +# D. YOU FURTHER ACKNOWLEDGE THAT THIS SHELL SCRIPT AND SERVICES ARE NOT +# INTENDED OR SUITABLE FOR USE IN SITUATIONS OR ENVIRONMENTS WHERE THE FAILURE +# OR TIME DELAYS OF, OR ERRORS OR INACCURACIES IN, THE CONTENT, DATA OR +# INFORMATION PROVIDED BY THIS SHELL SCRIPT OR SERVICES COULD LEAD TO +# DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE, +# INCLUDING WITHOUT LIMITATION THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT +# NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, LIFE SUPPORT OR +# WEAPONS SYSTEMS. +# +# E. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY THE AUTHOR +# SHALL CREATE A WARRANTY. SHOULD THIS SHELL SCRIPT OR SERVICES PROVE DEFECTIVE, +# YOU ASSUME THE ENTIRE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +# +# Limitation of Liability. +# F. TO THE EXTENT NOT PROHIBITED BY APPLICABLE LAW, IN NO EVENT SHALL THE AUTHOR +# BE LIABLE FOR PERSONAL INJURY, OR ANY INCIDENTAL, SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES WHATSOEVER, INCLUDING, WITHOUT LIMITATION, DAMAGES +# FOR LOSS OF PROFITS, CORRUPTION OR LOSS OF DATA, FAILURE TO TRANSMIT OR +# RECEIVE ANY DATA OR INFORMATION, BUSINESS INTERRUPTION OR ANY OTHER +# COMMERCIAL DAMAGES OR LOSSES, ARISING OUT OF OR RELATED TO YOUR USE OR +# INABILITY TO USE THIS SHELL SCRIPT OR SERVICES OR ANY THIRD PARTY +# SOFTWARE OR APPLICATIONS IN CONJUNCTION WITH THIS SHELL SCRIPT OR +# SERVICES, HOWEVER CAUSED, REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, +# TORT OR OTHERWISE) AND EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +# OR LIMITATION OF LIABILITY FOR PERSONAL INJURY, OR OF INCIDENTAL OR +# CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. In no event +# shall THE AUTHOR's total liability to you for all damages (other than as may +# be required by applicable law in cases involving personal injury) exceed +# the amount of five dollars ($5.00). The foregoing limitations will apply +# even if the above stated remedy fails of its essential purpose. +################################################################################ + +ulimit -t 1200 +PATH="/bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:${PATH}" +LANG=${LANG:-"en_US"} +LC_ALL="${LANG:1:5}.utf-8" +umask 127 + +LOCK_FILE="${TMPDIR:-/tmp}/codecov_test_script_lock" +EXIT_CODE=0 + +test -x $(command -v grep) || exit 126 ; +test -x $(command -v curl) || exit 126 ; +test -x $(command -v gpgv) || exit 126 ; +test -x $(command -v shasum) || exit 126 ; + +# sorry no windows support here +if [[ $( \uname -s ) == "Darwin" ]] ; then + CI_OS="macos" +else + CI_OS="linux" +fi + +function cleanup() { + rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; + rm -f ./codecov 2>/dev/null || true ; wait ; +} + +if [[ ( $(shlock -f ${LOCK_FILE} -p $$ ) -eq 0 ) ]] ; then + trap 'cleanup ; wait ; exit 1 ;' SIGHUP || EXIT_CODE=3 + trap 'cleanup ; wait ; exit 1 ;' SIGTERM || EXIT_CODE=4 + trap 'cleanup ; wait ; exit 1 ;' SIGQUIT || EXIT_CODE=5 + # SC2173 - https://github.com/koalaman/shellcheck/wiki/SC2173 + # trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGSTOP || EXIT_CODE=7 + trap 'cleanup ; wait ; exit 1 ;' SIGINT || EXIT_CODE=8 + trap 'cleanup ; wait ; exit 1 ;' SIGABRT || EXIT_CODE=9 + trap 'cleanup ; wait ; exit ${EXIT_CODE} ;' EXIT || EXIT_CODE=1 +else + echo "CodeCov already in progress by "`head ${LOCK_FILE}` ; + false ; + exit 126 ; +fi + +# this is how test files are found: + +# THIS IS THE ACTUAL TEST DIR USED (update _TEST_ROOT_DIR as needed) +_TEST_ROOT_DIR="./" ; +if [[ -d ./multicast ]] ; then + _TEST_ROOT_DIR="./" ; +elif [[ -d ./tests ]] ; then + _TEST_ROOT_DIR="./" ; +else + echo "FAIL: missing valid folder or file" + EXIT_CODE=1 +fi + +# This File MUST BE GIT-IGNORED +# to be SAFELY USED to store Tokens and env vars (update logic as needed) +if [[ ( -r ./codecov_env ) ]] ; then + source ./codecov_env 2>/dev/null || true ; +fi + +if [[ ( -r ./codecov.yml ) ]] ; then + cat codecov.yml | curl -X POST --data-binary @- https://codecov.io/validate 2>/dev/null || EXIT_CODE=6 +fi +if [[ ( -r ./.codecov.yml ) ]] ; then + cat ./.codecov.yml | curl -X POST --data-binary @- https://codecov.io/validate 2>/dev/null || EXIT_CODE=6 +fi + + +######################### +# actual Work starts here +######################### +curl -fLso codecov https://uploader.codecov.io/latest/${CI_OS:-linux}/codecov ; +for i in 1 256 512 ; do + curl -fLso codecov.SHA${i}SUM "https://uploader.codecov.io/latest/${CI_OS:-linux}/codecov.SHA${i}SUM" ; wait ; + curl -fLso codecov.SHA${i}SUM.sig "https://uploader.codecov.io/latest/${CI_OS:-linux}/codecov.SHA${i}SUM.sig" ; wait ; + # test sha1/sha512 signatures if found and sha256 even if not found + if [[ ( -r codecov.SHA${i}SUM ) ]] || [[ ( ${i} -eq 256 ) ]] ; then + if [[ ( -r codecov.SHA${i}SUM.sig ) ]] ; then + # configure your CI evironment to trust the key at https://keybase.io/codecovsecurity/pgp_keys.asc + # FP: 2703 4E7F DB85 0E0B BC2C 62FF 806B B28A ED77 9869 + # OR... + # Set CI=true to continue on missing keys + gpgv codecov.SHA${i}SUM.sig codecov.SHA${i}SUM || ${CI} || EXIT_CODE=126 + rm -vf codecov.SHA${i}SUM.sig 2>/dev/null ; + fi + shasum -a $i -c --ignore-missing codecov.SHA${i}SUM || EXIT_CODE=126 + rm -vf codecov.SHA${i}SUM 2>/dev/null ; + fi +done + +if [[ ( ${EXIT_CODE} -eq 0 ) ]] ; then + chmod -v 751 ./codecov || EXIT_CODE=126 +fi + +if [[ ( -x $(command -v coverage3) ) ]] ; then + coverage3 combine 2>/dev/null || true + coverage3 xml 2>/dev/null || true +elif [[ ( -x $(command -v coverage) ) ]] ; then + coverage combine 2>/dev/null || true + coverage xml 2>/dev/null || true +fi + +if [[ ( ${EXIT_CODE} -eq 0 ) ]] ; then + ./codecov -n "Custom Test Run" -X gcov -F multicast,${CI_OS:-linux}-latest -Z || EXIT_CODE=10 ; +fi + + +unset _TEST_ROOT_DIR 2>/dev/null || true ; + +rm -f ./codecov 2>/dev/null > /dev/null || true ; wait ; +rm -f ${LOCK_FILE} 2>/dev/null > /dev/null || true ; wait ; + +# goodbye +exit ${EXIT_CODE:-255} ; diff --git a/tests/check_spelling b/tests/check_spelling index 53747fa1..ecbc3b5a 100755 --- a/tests/check_spelling +++ b/tests/check_spelling @@ -64,7 +64,7 @@ ulimit -t 600 PATH="/bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" umask 137 -LOCK_FILE="/tmp/spelling_test_script_lock" +LOCK_FILE="${TMPDIR:-/tmp}/spelling_test_script_lock" EXIT_CODE=0 # exit fast if command is missing @@ -74,7 +74,8 @@ if [[ ( $(shlock -f ${LOCK_FILE} -p $$ ) -eq 0 ) ]] ; then trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGHUP || EXIT_CODE=3 trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGTERM || EXIT_CODE=4 trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGQUIT || EXIT_CODE=5 - trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGSTOP || EXIT_CODE=7 + # SC2173 - https://github.com/koalaman/shellcheck/wiki/SC2173 + # trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGSTOP || EXIT_CODE=7 trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGINT || EXIT_CODE=8 trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit 1 ;' SIGABRT || EXIT_CODE=9 trap 'rm -f ${LOCK_FILE} 2>/dev/null || true ; wait ; exit ${EXIT_CODE} ;' EXIT || EXIT_CODE=1 diff --git a/tests/context.py b/tests/context.py index ac1a6382..0d70c393 100644 --- a/tests/context.py +++ b/tests/context.py @@ -23,27 +23,27 @@ __doc__ = """ Context for Testing. - + Meta Tests - Fixtures: - + Test fixtures by importing test context. - + >>> import tests.context as context >>> >>> from context import os as os >>> - >>> from context import unittest as unittest + >>> from context import unittest as _unittest >>> - >>> from context import subprocess as subprocess + >>> from context import subprocess as _subprocess >>> - >>> from context import multicast as multicast + >>> from context import multicast as _multicast >>> - >>> from context import profiling as profiling + >>> from context import profiling as _profiling >>> """ @@ -101,49 +101,137 @@ raise ImportError("[CWE-440] profiling Failed to import.") -def getPythonCommand(): +__BLANK = str("""""") +""" + A literaly named variable to improve readability of code when using a blank string. + + Meta Testing: + + First setup test fixtures by importing test context. + + >>> import tests.context as _context + >>> + + Testcase 1: __BLANK should be a blank string. + + >>> import tests.context as _context + >>> _context.__BLANK is None + False + >>> isinstance(_context.__BLANK, type(str())) + True + >>> len(_context.__BLANK) == int(0) + True + >>> + + +""" + + +def getCoverageCommand(): """ - Function for backend python command. + Function for backend coverage command. Rather than just return the sys.executable which will usually be a python implementation, this function will search for a coverage tool to allow coverage testing to continue beyond the process fork of typical cli testing. Meta Testing: + First setup test fixtures by importing test context. + + >>> import tests.context as context + >>> + + Testcase 1: function should have a output. + + >>> import tests.context as context + >>> context.getCoverageCommand() is None + False + >>> + + + """ + thecov = "exit 1 ; #" + try: + thecov = checkPythonCommand(["command", "-v", "coverage"]) + if (str("/coverage") in str(thecov)): + thecov = str("coverage") + elif str("/coverage3") in str(checkPythonCommand(["command", "-v", "coverage3"])): + thecov = str("coverage3") + else: # pragma: no branch + thecov = "exit 1 ; #" + except Exception: # pragma: no branch + thecov = "exit 1 ; #" + return str(thecov) + + +def __check_cov_before_py(): + """ + Utility Function to check for coverage availability before just using plain python. + Rather than just return the sys.executable which will usually be a python implementation, + this function will search for a coverage tool before falling back on just plain python. + + Meta Testing: + + First setup test fixtures by importing test context. + + >>> import tests.context + >>> + + Testcase 1: function should have a output. + + >>> tests.context.__check_cov_before_py() is not None + True + >>> + + Testcase 2: function should have a string output of python or coverage. + + >>> _test_fixture = tests.context.__check_cov_before_py() + >>> isinstance(_test_fixture, type(str(""))) + True + >>> (str("python") in _test_fixture) or (str("coverage") in _test_fixture) + True + >>> + + + """ + thepython = str(sys.executable) + thecov = getCoverageCommand() + if (str("coverage") in str(thecov)) and (sys.version_info >= (3, 7)): + thepython = str("{} run -p").format(str(thecov)) + else: # pragma: no branch + try: + import coverage as coverage + if coverage.__name__ is not None: + thepython = str("{} -m coverage run -p").format(str(sys.executable)) + except Exception: + thepython = str(sys.executable) + return str(thepython) + + +def getPythonCommand(): + """ + Function for backend python command. + Rather than just return the sys.executable which will usually be a python implementation, + this function will search for a coverage tool with getCoverageCommand() first. + + Meta Testing: + First setup test fixtures by importing test context. >>> import tests.context >>> Testcase 1: function should have a output. - + >>> tests.context.getPythonCommand() is not None True >>> """ - thepython = "exit 1 ; #" + thepython = "python" try: - thepython = checkPythonCommand(["command", "-v", "coverage"]) - if (sys.version_info >= (3, 3)): - if (str("/coverage") in str(thepython)) and (sys.version_info >= (3, 3)): - thepython = str("coverage run -p") - elif str("/coverage3") in str(checkPythonCommand(["command", "-v", "coverage3"])): - thepython = str("coverage3 run -p") - else: - thepython = str(sys.executable) - elif (str("/coverage") in str(thepython)) and (sys.version_info <= (3, 2)): - try: - import coverage - if coverage.__name__ is not None: - thepython = str("{} -m coverage run -p").format(str(sys.executable)) - else: - thepython = str(sys.executable) - except Exception: - thepython = str(sys.executable) - else: - thepython = str(sys.executable) - except Exception: + thepython = __check_cov_before_py() + except Exception: # pragma: no branch thepython = "exit 1 ; #" try: thepython = str(sys.executable) @@ -152,50 +240,57 @@ def getPythonCommand(): return str(thepython) -def checkPythonCommand(args=[None], stderr=None): +def checkCovCommand(args=[None]): + """Utility Function.""" + if sys.__name__ is None: # pragma: no branch + raise ImportError("[CWE-758] Failed to import system. WTF?!!") + if str("coverage") in args[0]: + i = 1 + if str("{} -m coverage").format(str(sys.executable)) in str(args[0]): # pragma: no branch + args[0] = str(sys.executable) + args.insert(1, str("-m")) + args.insert(2, str("coverage")) + i += 2 + else: # pragma: no branch + args[0] = str(getCoverageCommand()) + extra_args = ["""run""", """-p""", """--source=multicast"""] + # PEP-279 - see https://www.python.org/dev/peps/pep-0279/ + for k, ktem in enumerate(extra_args): + offset = i + k + args.insert(offset, ktem) + return args + + +def checkStrOrByte(theInput): + theOutput = None + if theInput is not None: + theOutput = theInput + try: + if isinstance(theInput, bytes): + theOutput = theInput.decode("""UTF-8""") + except UnicodeDecodeError: + theOutput = bytes(theInput) + return theOutput + + +def checkPythonCommand(args, stderr=None): """function for backend subprocess check_output command""" theOutput = None try: - if args is None or args is [None]: + if (args is None) or (args is [None]) or (len(args) <= 0): # pragma: no branch theOutput = subprocess.check_output(["exit 1 ; #"]) else: - if str("coverage ") in args[0]: - if sys.__name__ is None: - raise ImportError("[CWE-758] Failed to import system. WTF?!!") - if str("{} -m coverage ").format(str(sys.executable)) in str(args[0]): - args[0] = str(sys.executable) - args.insert(1, str("-m")) - args.insert(2, str("coverage")) - args.insert(3, str("run")) - args.insert(4, str("-p")) - args.insert(5, str("--source=multicast")) - elif str("{} -m coverage3 ").format(str(sys.executable)) in str(args[0]): - args[0] = str(sys.executable) - args.insert(1, str("-m")) - args.insert(2, str("coverage3")) - args.insert(3, str("run")) - args.insert(4, str("-p")) - args.insert(5, str("--source=multicast")) - else: - args[0] = str("coverage") - args.insert(1, str("run")) - args.insert(2, str("-p")) - args.insert(3, str("--source=multicast")) + if str("coverage") in args[0]: + args = checkCovCommand(args) theOutput = subprocess.check_output(args, stderr=stderr) - except Exception as err: + except Exception as err: # pragma: no branch theOutput = None try: if err.output is not None: theOutput = err.output - except Exception as cascadeErr: + except Exception: theOutput = None - cascadeErr = None - del cascadeErr - try: - if isinstance(theOutput, bytes): - theOutput = theOutput.decode("""utf_8""") - except UnicodeDecodeError: - theOutput = bytes(theOutput) + theOutput = checkStrOrByte(theOutput) return theOutput @@ -209,68 +304,104 @@ def checkPythonFuzzing(args=[None], stderr=None): """function for backend subprocess check_output command""" theOutput = None try: - if args is None or args is [None]: + if args is None or args is [None]: # pragma: no branch theOutput = subprocess.check_output(["exit 1 ; #"]) else: - if str("coverage ") in args[0]: - if sys.__name__ is None: - raise ImportError("Failed to import system. WTF?!!") - if str("{} -m coverage ").format(str(sys.executable)) in str(args[0]): - args[0] = str(sys.executable) - args.insert(1, str("-m")) - args.insert(2, str("coverage")) - args.insert(3, str("run")) - args.insert(4, str("-p")) - args.insert(4, str("--source=multicast")) - else: - args[0] = str("coverage") - args.insert(1, str("run")) - args.insert(2, str("-p")) - args.insert(2, str("--source=multicast")) + if str("coverage") in args[0]: + args = checkCovCommand(args) theOutput = subprocess.check_output(args, stderr=stderr) - if isinstance(theOutput, bytes): - theOutput = theOutput.decode('utf_8') - except Exception as err: + except BaseException as err: # pragma: no branch theOutput = None raise RuntimeError(err) + theOutput = checkStrOrByte(theOutput) return theOutput def debugBlob(blob=None): + """Helper function to debug unexpected outputs. + + Especialy usefull for cross-python testing where output may differ + yet may be from the same logical data. + + Meta Testing: + + First setup test fixtures by importing test context. + + >>> import tests.context + >>> + + >>> norm_fixture = "Example Sample" + >>> othr_fixture = \"""'Example Sample'\""" + >>> + + Testcase 1: function should have a output. + + >>> debugBlob(norm_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + + String: + " + Example Sample + " + + Data: + " + 'Example Sample' + " + + True + >>> + + Testcase 2: function should have a output even with bad input. + + >>> debugBlob(othr_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + + String: + " + ...Example Sample... + " + + Data: + " + ...'Example Sample'... + " + + True + >>> + + """ try: - print(str("")) + print(__BLANK) print(str("String:")) print(str("""\"""")) print(str(blob)) print(str("""\"""")) - print(str("")) + print(__BLANK) print(str("Data:")) print(str("""\"""")) print(repr(blob)) print(str("""\"""")) - print(str("")) + print(__BLANK) except Exception: - return False + print(__BLANK) return True def debugtestError(someError): - """ - Helper function to debug unexpected outputs. - + """Helper function to debug unexpected outputs. + Meta Testing: - + First setup test fixtures by importing test context. - + >>> import tests.context >>> - + >>> err_fixture = RuntimeError(\"Example Error\") >>> bad_fixture = BaseException() >>> - + Testcase 1: function should have a output. - + >>> debugtestError(err_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS ERROR: @@ -279,9 +410,9 @@ def debugtestError(someError): ('Example Error',) >>> - + Testcase 2: function should have a output even with bad input. - + >>> debugtestError(bad_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS ERROR: @@ -292,7 +423,7 @@ def debugtestError(someError): >>> """ - print(str("")) + print(__BLANK) print(str("ERROR:")) if someError is not None: print(str(type(someError))) @@ -301,12 +432,14 @@ def debugtestError(someError): print(str((someError.args))) else: print(str("")) - print(str("")) + print(__BLANK) def check_exec_command_has_output(test_case, someArgs): """Test case for command output != None. - returns True if has output and False otherwise.""" + + returns True if has output and False otherwise. + """ theResult = False fail_msg_fixture = str("""Expecting output: CLI test had no output.""") try: @@ -329,8 +462,7 @@ def check_exec_command_has_output(test_case, someArgs): def debugUnexpectedOutput(expectedOutput, actualOutput, thepython): - """ - Helper function to debug unexpected outputs. + """Helper function to debug unexpected outputs. Meta Testing: @@ -363,7 +495,7 @@ def debugUnexpectedOutput(expectedOutput, actualOutput, thepython): >>> Testcase 2: function should have a output even with bad input. - + >>> tests.context.debugUnexpectedOutput( ... expected_fixture, unexpected_fixture, None ... ) #doctest: -DONT_ACCEPT_BLANKLINE @@ -381,21 +513,21 @@ def debugUnexpectedOutput(expectedOutput, actualOutput, thepython): >>> """ - print(str("")) + print(__BLANK) if (thepython is not None): print(str("python cmd used: {}").format(str(thepython))) else: print("Warning: Unexpected output!") - print(str("")) + print(__BLANK) if (expectedOutput is not None): print(str("The expected output is...")) - print(str("")) + print(__BLANK) print(str("{}").format(str(expectedOutput))) - print(str("")) + print(__BLANK) print(str("The actual output was...")) - print(str("")) + print(__BLANK) print(str("{}").format(str(actualOutput))) - print(str("")) + print(__BLANK) class BasicUsageTestSuite(unittest.TestCase): @@ -430,11 +562,11 @@ class BasicUsageTestSuite(unittest.TestCase): @classmethod def setUpClass(cls): + """Overides unittest.TestCase.setUpClass(cls) to setup thepython test fixture.""" cls._thepython = getPythonCommand() - + def setUp(self): - """ - Overides unittest.TestCase.setUp(unittest.TestCase). + """Overides unittest.TestCase.setUp(unittest.TestCase). Defaults is to skip test if class is missing thepython test fixture. """ if (self._thepython is None) and (len(self._thepython) <= 0): @@ -446,6 +578,12 @@ def test_absolute_truth_and_meaning(self): assert True self.assertTrue(True, "Insanitty Test Failed") + def test_finds_python_WHEN_testing(self): + """Test case 1: Class Test-Fixture Meta Test.""" + if (self._thepython is None) and (len(self._thepython) <= 0): + self.fail(str("""No python cmd to test with!""")) + self.test_absolute_truth_and_meaning() + @classmethod def tearDownClass(cls): """Overides unittest.TestCase.tearDownClass(cls) to clean up thepython test fixture.""" diff --git a/tests/profiling.py b/tests/profiling.py index 687b9169..68ae2f57 100644 --- a/tests/profiling.py +++ b/tests/profiling.py @@ -18,7 +18,7 @@ # limitations under the License. -# Third-party Acknowlegement: +# Third-party Acknowledgement: # .......................................... # Some code (namely: class timewith, @do_cprofile, @do_line_profile) was modified/derived from: # https://github.com/zapier/profiling-python-like-a-boss/tree/1ab93a1154 @@ -109,7 +109,34 @@ def __exit__(self, type, value, traceback): def do_time_profile(func, timer_name="time_profile"): - """Runs a function with a timer""" + """Runs a function with a timer. + + Time Testing: + + First some test fixtures: + + >>> import tests.context as context + >>> from context import profiling as profiling + >>> + + Testcase 0: test the time_profile. + + >>> def doWork(): + ... \"""Does some work.\""" + ... for i in range(0, 42): + ... print(str("Do Task {}").format(int(i))) + >>> + >>> profiling.do_time_profile( + ... doWork, + ... timer_name=str("work time test") + ... )() #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + work...Start Timer... + ...Do Task... + work...Stop Timer... + work...took ... seconds + >>> + + """ import functools @functools.wraps(func) @@ -126,7 +153,36 @@ def timer_profile_func(*args, **kwargs): def do_cprofile(func): - """use built-in profiler to profile.""" + """Use built-in profiler to profile. + + Time Testing: + + First some test fixtures: + + >>> import tests.context as context + >>> from context import profiling as profiling + >>> + + Testcase 0: test the time_profile. + + >>> def doWork(): + ... \"""Does some work.\""" + ... for i in range(0, 42): + ... print(str("Do Task {}").format(int(i))) + >>> + >>> profiling.do_cprofile( + ... doWork + ... )() #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + Do Task 0...Do Task 10...Do Task 20...Do Task 30...Do Task 40... + ...function calls in ... seconds...Ordered by: standard name... + ...ncalls tottime percall cumtime percall filename:lineno(function)... + ...<...>:1(doWork)...{built-in method builtins.print}... + + + >>> + + + """ def profiled_func(*args, **kwargs): profile = cProfile.Profile() try: @@ -142,7 +198,7 @@ def profiled_func(*args, **kwargs): try: # noqa from line_profiler import LineProfiler - def do_profile(follow=None): + def do_profile(follow=None): # pragma: no cover if follow is None: follow = [] @@ -160,7 +216,7 @@ def profiled_func(*args, **kwargs): return profiled_func return inner -except ImportError: +except ImportError: # pragma: no cover def do_profile(follow=None): "Helpful if you accidentally leave in production!" if follow is None: @@ -173,16 +229,15 @@ def nothing(*args, **kwargs): return inner -def main(argv=None): +def main(argv=None): # pragma: no cover """The Main Event makes no sense to profiling.""" raise NotImplementedError("CRITICAL - test profiling main() not implemented. yet?") -if __name__ in '__main__': +if __name__ in '__main__': # pragma: no cover exitcode = 3 try: exitcode = main(sys.argv[1:]) finally: exit(exitcode) - diff --git a/tests/test_basic.py b/tests/test_basic.py index f09edbed..7a2016fb 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -17,18 +17,8 @@ # See the License for the specific language governing permissions and # limitations under the License. - __module__ = """tests""" - -try: - import sys - if sys.__name__ is None: # pragma: no branch - raise ImportError("[CWE-440] OMG! we could not import sys! ABORT. ABORT.") -except Exception as err: # pragma: no branch - raise ImportError(err) - - try: try: import context @@ -40,7 +30,8 @@ raise ImportError("[CWE-758] Failed to import context") else: from context import unittest as unittest -except Exception: + from context import sys as _sys +except Exception: # pragma: no branch raise ImportError("[CWE-758] Failed to import test context") @@ -54,29 +45,31 @@ def test_absolute_truth_and_meaning(self): """Insanitty Test 1: Because it only matters if we're not mad as hatters.""" assert True - def test_meta_test(self): + def test_Does_Pass_WHEN_Meta_Test(self): """Insanity Test 2: for unittests assertion.""" self.assertTrue(True) self.assertFalse(False) self.assertIsNone(None) + self.test_absolute_truth_and_meaning() + self.test_None_WHEN_Nothing() - def test_syntax(self): + def test_Does_Pass_WHEN_Using_Import_From_Syntax(self): """Test case 0: importing multicast.""" theResult = False try: from .context import multicast self.assertIsNotNone(multicast.__name__) - if multicast.__name__ is None: - theResult = False + self.assertIsNotNone(multicast.__module__) + self.assertIsNotNone(multicast.__doc__) theResult = True except Exception as impErr: print(str(type(impErr))) print(str(impErr)) theResult = False - assert theResult + self.assertTrue(theResult) - def test_the_help_command(self): - """Test case 1: import for backend library.""" + def test_Error_WHEN_the_help_command_is_called(self): + """Test case 1: the --help options should error when called.""" theResult = False try: from .context import multicast @@ -90,9 +83,9 @@ def test_the_help_command(self): theResult = True except Exception: theResult = False - assert theResult + self.assertTrue(theResult) - def test_corner_case_example(self): + def test_IsNone_WHEN_given_corner_case_input(self): """Example Test case for bad input directly into function.""" theResult = False try: @@ -105,17 +98,22 @@ def test_corner_case_example(self): theResult = True except Exception: theResult = False - assert theResult + self.assertTrue(theResult) - def test_new_tests(self): + def test_None_WHEN_Nothing(self): """Try adding new tests.""" self.assertIsNone(None) # define new tests below - @unittest.skipUnless(sys.platform.startswith("linux"), "This test example requires linux") - def test_this_linux_only(self): + @unittest.skipUnless(_sys.platform.startswith("linux"), "This test example requires linux") + def test_Skip_UNLESS_linux_only(self): """Linux is the test.""" - self.assertTrue(sys.platform.startswith("linux")) + self.assertTrue(_sys.platform.startswith("linux")) + + @unittest.skipUnless(_sys.platform.startswith("darwin"), "This test example requires macOS") + def test_Skip_UNLESS_darwin_only(self): + """MacOS is the test.""" + self.assertTrue(_sys.platform.startswith("darwin")) # leave this part diff --git a/tests/test_usage.py b/tests/test_usage.py index 328d50b6..5dc6e2e0 100644 --- a/tests/test_usage.py +++ b/tests/test_usage.py @@ -17,6 +17,35 @@ # See the License for the specific language governing permissions and # limitations under the License. + +""" +Tests of integration by usage. + + + Meta + tests.test_usage.BasicIntegrationTestSuite + + Integration Tests - Fixtures: + + Test fixtures by importing test context. + + >>> import tests.test_usage as test_usage + >>> import tests + >>> + + >>> tests.test_usage.MulticastTestSuite #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + + >>> + + >>> tests.test_usage.BasicIntegrationTestSuite #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS + + >>> + +""" + +__module__ = """tests""" + + try: import sys if sys.__name__ is None: # pragma: no branch @@ -35,11 +64,10 @@ if context.__name__ is None: raise ImportError("[CWE-758] Failed to import context") else: - from context import multicast + from context import multicast as multicast from context import unittest as unittest - from context import os as os from context import subprocess as subprocess - from context import profiling as profiling + from multiprocessing import Process except Exception: raise ImportError("[CWE-758] Failed to import test context") @@ -49,7 +77,9 @@ class MulticastTestSuite(context.BasicUsageTestSuite): __module__ = """tests.test_usage""" - def test_multicast_insane_none(self): + __name__ = """tests.test_usage.MulticastTestSuite""" + + def test_aborts_WHEN_calling_multicast_GIVEN_invalid_tools(self): """Tests the imposible state for CLI tools given bad tools""" theResult = False fail_fixture = str("""multicast.__main__.useTool(JUNK) == error""") @@ -63,12 +93,13 @@ def test_multicast_insane_none(self): theResult = False self.assertTrue(theResult, fail_fixture) - def test_multicast_message_arg_main(self): - """Tests the message argument for failure given future tools""" + def test_say_is_stable_WHEN_calling_multicast_GIVEN_say_tool(self): + """Tests the message argument for expected syntax given simple args""" theResult = False fail_fixture = str("""multicast.__main__.useTool(SAY, message) == error""") try: - self.assertIsNotNone(multicast.__main__.useTool("SAY", ["--message"])) + with self.assertRaises(SystemExit): + self.assertIsNotNone(multicast.__main__.useTool("SAY", ["--message"])) self.assertIsNotNone(multicast.__main__.useTool("SAY", ["--message", "test"])) theResult = True except Exception as err: @@ -77,13 +108,29 @@ def test_multicast_message_arg_main(self): theResult = False self.assertTrue(theResult, fail_fixture) - @unittest.expectedFailure - def test_multicast_hexdump_arg_main(self): + def test_hear_aborts_WHEN_calling_multicast_GIVEN_invalid_args(self): + """Tests the message argument for failure given invalid input""" + theResult = False + fail_fixture = str("""multicast.__main__.useTool(HEAR, junk) != 2""") + try: + with self.assertRaises(SystemExit): + self.assertIsNotNone(multicast.__main__.useTool("HEAR", ["--port", "test"])) + self.assertNotEqual(multicast.__main__.useTool("HEAR", ["--port", "test"]), 0) + self.assertNotEqual(multicast.__main__.useTool("HEAR", ["--port", "test"]), 1) + self.assertNotEqual(multicast.__main__.useTool("HEAR", ["--port", "11911", "--group=None"]), 1) + theResult = True + except Exception as err: + context.debugtestError(err) + self.fail(fail_fixture) + theResult = False + self.assertTrue(theResult, fail_fixture) + + def test_hear_aborts_WHEN_calling_multicast_GIVEN_invalid_tool(self): """Tests the hexdump argument for failure given future tools""" theResult = False fail_fixture = str("""multicast.__main__.useTool(HEAR, hex) == error""") try: - with self.assertRaises(NotImplementedError, fail_fixture): + with self.assertRaises(SystemExit): self.assertIsNotNone(multicast.__main__.useTool("HEAR", ["--hex"])) theResult = True except Exception as err: @@ -92,7 +139,7 @@ def test_multicast_hexdump_arg_main(self): theResult = False self.assertTrue(theResult, fail_fixture) - def test_multicast_invalid_main(self): + def test_noop_stable_WHEN_calling_multicast_GIVEN_noop_args(self): """Tests the NOOP state for multicast given bad input""" theResult = False fail_fixture = str("""multicast.__main__.main(NOOP) == empty""") @@ -105,7 +152,7 @@ def test_multicast_invalid_main(self): theResult = False self.assertTrue(theResult, fail_fixture) - def test_multicast_help_arg_main(self): + def test_help_works_WHEN_calling_multicast_GIVEN_help_tool(self): """Tests the HELP argument for help usage""" theResult = False fail_fixture = str("""multicast.__main__.useTool(HELP, []) == error""") @@ -118,53 +165,81 @@ def test_multicast_help_arg_main(self): theResult = False self.assertTrue(theResult, fail_fixture) - -def debugIfNoneResult(thepython, theArgs, theOutput): - """In case you need it.""" - try: - if (str(theOutput) is not None): + def test_hear_works_WHEN_say_works(self): + """Tests the basic send and recv test""" + theResult = False + fail_fixture = str("""SAY --> HEAR == error""") + try: + _fixture_SAY_args = [ + """--port=19991""", + """--mcast-group='224.0.0.1'""", + """--message='test message'""" + ] + _fixture_HEAR_args = [ + """--port=19991""", + """--join-mcast-groups='224.0.0.1'""", + """--bind-group='224.0.0.1'""" + ] + p = Process(target=multicast.__main__.useTool, name="HEAR", args=("HEAR", _fixture_HEAR_args,)) + p.start() + try: + self.assertIsNotNone(multicast.__main__.useTool("SAY", _fixture_SAY_args)) + self.assertIsNotNone(multicast.__main__.useTool("SAY", _fixture_SAY_args)) + self.assertIsNotNone(multicast.__main__.useTool("SAY", _fixture_SAY_args)) + except Exception: + p.join() + raise unittest.SkipTest(fail_fixture) + p.join() + self.assertIsNotNone(p.exitcode) + self.assertEqual(int(p.exitcode), int(0)) theResult = True - else: + except Exception as err: + context.debugtestError(err) + # raise unittest.SkipTest(fail_fixture) + self.fail(fail_fixture) theResult = False - context.debugUnexpectedOutput(theOutput, None, thepython) - except Exception: - theResult = False - return theResult + self.assertTrue(theResult, fail_fixture) class BasicIntegrationTestSuite(context.BasicUsageTestSuite): """Basic functional test cases.""" + __module__ = """tests.test_usage""" + + __name__ = """tests.test_usage.BasicIntegrationTestSuite""" + def setUp(self): super(self.__class__, self).setUp() if (self._thepython is None): self.skipTest(str("""No python cmd to test with!""")) - def test_run_lib_command_plain(self): + def test_prints_usage_WHEN_called_GIVEN_help_argument(self): """Test case for multicast.__main__ help.""" theResult = False + fail_fixture = str("""multicast.__main__(--help) == not helpful""") try: if (self._thepython is not None): - theOutputtext = context.checkPythonCommand([ + theOutputtxt = context.checkPythonCommand([ str(self._thepython), str("-m"), str("multicast"), str("--help") ], stderr=subprocess.STDOUT) - self.assertIn(str("usage:"), str(theOutputtext)) - if (str("usage:") in str(theOutputtext)): + self.assertIn(str("usage:"), str(theOutputtxt)) + if (str("usage:") in str(theOutputtxt)): theResult = True else: theResult = False - context.debugUnexpectedOutput(str("usage:"), str(theOutputtext), self._thepython) + context.debugUnexpectedOutput(str("usage:"), str(theOutputtxt), self._thepython) except Exception as err: context.debugtestError(err) err = None del err + self.fail(fail_fixture) theResult = False self.assertTrue(theResult, str("""Could Not find usage from multicast --help""")) - def test_run_lib_command_main(self): + def test_equivilant_response_WHEN_absolute_vs_implicit(self): """Test case for multicast vs multicast.__main__""" theResult = False try: @@ -174,17 +249,17 @@ def test_run_lib_command_main(self): str("multicast.__main__") ], stderr=subprocess.STDOUT) self.assertIsNotNone(theExpectedText) - theOutputtext = context.checkPythonCommand([ + theOutputtxt = context.checkPythonCommand([ str(self._thepython), str("-m"), str("multicast") ], stderr=subprocess.STDOUT) - self.assertIn(str(theExpectedText), str(theOutputtext)) - if (str(theExpectedText) in str(theOutputtext)): + self.assertIn(str(theExpectedText), str(theOutputtxt)) + if (str(theExpectedText) in str(theOutputtxt)): theResult = True else: theResult = False - context.debugUnexpectedOutput(str(theExpectedText), str(theOutputtext), self._thepython) + context.debugUnexpectedOutput(str(theExpectedText), str(theOutputtxt), self._thepython) except BaseException as err: context.debugtestError(err) err = None @@ -192,7 +267,7 @@ def test_run_lib_command_main(self): theResult = False self.assertTrue(theResult, str("""Could Not swap multicast for multicast.__main__""")) - def test_version_has_value_case(self): + def test_prints_version_WHEN_called_GIVEN_version_argument(self): """Test for result from --version argument: python -m multicast.* --version """ theResult = False if (self._thepython is not None): @@ -208,25 +283,60 @@ def test_version_has_value_case(self): ), str("--version") ] - theOutputtext = context.checkPythonCommand(args, stderr=subprocess.STDOUT) - # now test it - try: - if isinstance(theOutputtext, bytes): - theOutputtext = theOutputtext.decode('utf8') - except UnicodeDecodeError: - theOutputtext = str(repr(bytes(theOutputtext))) - # ADD REAL VERSION TEST HERE - theResult = debugIfNoneResult(self._thepython, args, theOutputtext) - # or simply: - self.assertIsNotNone(theOutputtext) + theOutputtxt = context.checkPythonCommand(args, stderr=subprocess.STDOUT) + context.check_exec_command_has_output(self, args) + theResult = (theOutputtxt is not None) except Exception as err: context.debugtestError(err) err = None del err theResult = False - assert theResult + self.assertTrue(theResult, str("""Could Not find version from multicast --version""")) - def test_profile_template_case(self): + def test_Usage_Error_WHEN_the_help_command_is_called(self): + """Test case for multicast* --help.""" + theResult = False + fail_fixture = str("""multicast --help == not helpful""") + try: + if (self._thepython is not None): + for test_case in [".__main__", ""]: + args = [ + str(self._thepython), + str("-m"), + str("multicast{}").format( + str( + test_case + ) + ), + str("--help") + ] + theOutputtxt = context.checkPythonCommand(args, stderr=subprocess.STDOUT) + context.debugBlob(theOutputtxt) + # now test it + try: + if isinstance(theOutputtxt, bytes): + theOutputtxt = theOutputtxt.decode('utf8') + except UnicodeDecodeError: + theOutputtxt = str(repr(bytes(theOutputtxt))) + # or simply: + self.assertIsNotNone(theOutputtxt) + self.assertIn(str("""usage:"""), str(theOutputtxt)) + if (str("""usage:""") in str(theOutputtxt)): + theResult = True or theResult + else: + theResult = False + context.debugUnexpectedOutput( + str("usage:"), str(theOutputtxt), self._thepython + ) + except Exception as err: + context.debugtestError(err) + err = None + del err + self.fail(fail_fixture) + theResult = False + self.assertTrue(theResult, str("""Could Not find usage from multicast --help""")) + + def test_profile_WHEN_the_noop_command_is_called(self): """Test case template for profiling""" theResult = False if (self._thepython is not None): @@ -242,15 +352,15 @@ def test_profile_template_case(self): ) ) ] - theOutputtext = context.timePythonCommand(args, stderr=subprocess.STDOUT) + theOutputtxt = context.timePythonCommand(args, stderr=subprocess.STDOUT) # now test it try: - if isinstance(theOutputtext, bytes): - theOutputtext = theOutputtext.decode('utf8') + if isinstance(theOutputtxt, bytes): + theOutputtxt = theOutputtxt.decode('utf8') except UnicodeDecodeError: - theOutputtext = str(repr(bytes(theOutputtext))) + theOutputtxt = str(repr(bytes(theOutputtxt))) # or simply: - self.assertIsNotNone(theOutputtext) + self.assertIsNotNone(theOutputtxt) theResult = True except Exception as err: context.debugtestError(err) @@ -259,13 +369,12 @@ def test_profile_template_case(self): theResult = False assert theResult - # @unittest.expectedFailure - def test_fail_message_works_case(self): + def test_stable_WHEN_the_noop_command_is_called(self): """Test case template for profiling""" theResult = False if (self._thepython is not None): try: - for test_case in ["BAdInPut"]: + for test_case in ["NOOP"]: args = [ str(self._thepython), str("-m"), @@ -276,16 +385,38 @@ def test_fail_message_works_case(self): ) ) ] - theOutputtext = context.timePythonCommand(args, stderr=subprocess.STDOUT) + context.checkPythonFuzzing(args, stderr=None) # now test it - try: - if isinstance(theOutputtext, bytes): - theOutputtext = theOutputtext.decode('utf8') - except UnicodeDecodeError: - theOutputtext = str(repr(bytes(theOutputtext))) - theResult = debugIfNoneResult(self._thepython, args, theOutputtext) + theResult = True + except Exception as err: + context.debugtestError(err) + err = None + del err + theResult = False + self.assertTrue(theResult, str("""Could Not handle multicast NOOP""")) + + def test_invalid_Error_WHEN_cli_called_GIVEN_bad_input(self): + """Test case template for invalid input to multicast CLI.""" + theResult = False + if (self._thepython is not None): + try: + for test_case in ["BAdInPut", "1", "exit"]: + args = [ + str(self._thepython), + str("-m"), + str("multicast"), + str("{}").format( + str( + test_case + ) + ) + ] + theOutputtxt = context.checkPythonCommand(args, stderr=subprocess.STDOUT) # or simply: - self.assertIsNotNone(theOutputtext) + self.assertIsNotNone(theOutputtxt) + self.assertIn(str("invalid choice:"), str(theOutputtxt)) + self.assertIn(str(test_case), str(theOutputtxt)) + theResult = True except Exception as err: context.debugtestError(err) err = None diff --git a/tox.ini b/tox.ini index 3066c15e..27ef11b7 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ sitepackages = False recreate = True alwayscopy = True passenv = - LC_ALL="en_utf8" + LC_ALL="en_US.utf8" {[base]passenv} basepython = py27: python2.7