Repository: iterative/shtab (language: python)

Issue:
BUG: `tests` module is included in distribution
In conda, I get the following error:

```
ClobberError: This transaction has incompatible packages due to a shared path.
  packages: conda-forge/noarch::pyprojroot-0.2.0-py_0, conda-forge/noarch::shtab-1.3.4-pyhd3deb0d_0
  path: 'lib/site-packages/tests/__init__.py'
```

This suggests that this package is distributing a module called `tests`, which is wrong.

I assume that the same problem will occur with pip too.

In my experience, the best solution is a `src` layout as described in https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure and https://hynek.me/articles/testing-packaging/#src.

It's also worth noting that the "tests outside application code" pattern usually doesn't make the `tests` directory into a module: https://docs.pytest.org/en/latest/goodpractices.html?highlight=src#tests-outside-application-code. Note that this good practice guide also **strongly** suggests a src layout.


Discussion / hints:
Ah yes should be excluded indeed.

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9d687f1..2fcbb14 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,31 +2,43 @@ default_language_version:
   python: python3
 repos:
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v2.4.0
+  rev: v3.4.0
   hooks:
   - id: check-added-large-files
   - id: check-case-conflict
   - id: check-docstring-first
   - id: check-executables-have-shebangs
   - id: check-toml
+  - id: check-merge-conflict
   - id: check-yaml
+  - id: debug-statements
   - id: end-of-file-fixer
   - id: mixed-line-ending
+  - id: sort-simple-yaml
   - id: trailing-whitespace
-- hooks:
+- repo: local
+  hooks:
+  - id: todo
+    name: Check TODO
+    language: pygrep
+    entry: TODO
+    types: [text]
+    exclude: ^(.pre-commit-config.yaml|.github/workflows/test.yml)$
+    args: [-i]
+- repo: https://gitlab.com/pycqa/flake8
+  rev: 3.8.4
+  hooks:
   - id: flake8
     additional_dependencies:
     - flake8-bugbear
     - flake8-comprehensions
     - flake8-debugger
     - flake8-string-format
-  repo: https://gitlab.com/pycqa/flake8
-  rev: 3.8.4
-- hooks:
-  - id: black
-  repo: https://github.com/psf/black
+- repo: https://github.com/psf/black
   rev: 19.10b0
-- hooks:
+  hooks:
+  - id: black
+- repo: https://github.com/PyCQA/isort
+  rev: 5.7.0
+  hooks:
   - id: isort
-  repo: https://github.com/timothycrosley/isort
-  rev: 5.6.4
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 47df910..4d8201c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -5,9 +5,10 @@
 When contributing pull requests, it's a good idea to run basic checks locally:
 
 ```bash
-shtab (master)$ pip install .[dev]  # install development dependencies
+# install development dependencies
+shtab (master)$ pip install pre-commit -r requirements-dev.txt
 shtab (master)$ pre-commit install  # install pre-commit checks
-shtab (master)$ python -m tests     # run all tests
+shtab (master)$ pytest              # run all tests
 ```
 
 ## Layout
diff --git a/pyproject.toml b/pyproject.toml
index 9c5558c..d3e4264 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,4 +7,4 @@ write_to = "shtab/_dist_ver.py"
 write_to_template = "__version__ = '{version}'\n"
 
 [tool.black]
-target-version = ['py27', 'py36']
+target_version = ["py27", "py36", "py38"]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 3206080..08798aa 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,5 +1,3 @@
-pre-commit
-twine
-wheel
 pytest
 pytest-cov
+pytest-timeout
diff --git a/setup.cfg b/setup.cfg
index 3f403cc..9aab8a1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -70,6 +70,8 @@ packages=find:
 [options.entry_points]
 console_scripts=
     shtab=shtab.main:main
+[options.packages.find]
+exclude=tests
 [bdist_wheel]
 universal=1
 
@@ -81,3 +83,7 @@ exclude=build,dist,.eggs,.git,__pycache__
 [isort]
 profile=black
 known_first_party=shtab
+
+[tool:pytest]
+timeout=5
+addopts=-v --tb=short -rxs -W=error --log-level=debug --durations=0 --cov=shtab --cov-report=term-missing --cov-report=xml


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fe36280..6f4f8cb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -18,9 +18,7 @@ jobs:
       with:
         path: ~/.cache/pre-commit
         key: pre-commit|${{ env.PYSHA }}|${{ hashFiles('.pre-commit-config.yaml') }}
-    - run: pip install -U -r requirements-dev.txt
-    - run: python setup.py sdist bdist_wheel
-    - run: twine check dist/*
+    - run: pip install -U pre-commit
     - uses: reviewdog/action-setup@v1
     - run: |
         pre-commit run -a flake8 | reviewdog -f=pep8 -name=flake8 -tee -reporter=github-check -filter-mode nofilter
@@ -42,10 +40,9 @@ jobs:
       with:
         python-version: ${{ matrix.python }}
     - run: pip install -U -r requirements-dev.txt
-    - run: python -m tests --cov-report=term-missing
+    - run: pytest
     - uses: codecov/codecov-action@v1
   deploy:
-    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
     name: PyPI Deploy
     needs: [check, test]
     runs-on: ubuntu-latest
@@ -53,17 +50,17 @@ jobs:
     - uses: actions/checkout@v2
       with:
         fetch-depth: 0
-    - uses: casperdcl/deploy-pypi@v1
+    - uses: actions/setup-python@v2
+    - id: dist
+      uses: casperdcl/deploy-pypi@v2
       with:
-        password: ${{ secrets.PYPI_TOKEN }}
         build: true
-    - id: collect_assets
-      name: Collect assets
-      run: |
-        echo "::set-output name=asset_path::$(ls dist/*.whl)"
-        echo "::set-output name=asset_name::$(basename dist/*.whl)"
-        git log --pretty='format:%d%n- %s%n%b---' $(git tag --sort=v:refname | tail -n2 | head -n1)..HEAD > _CHANGES.md
-    - id: create_release
+        password: ${{ secrets.PYPI_TOKEN }}
+        upload: ${{ github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') }}
+    - name: Changelog
+      run: git log --pretty='format:%d%n- %s%n%b---' $(git tag --sort=v:refname | tail -n2 | head -n1)..HEAD > _CHANGES.md
+    - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+      id: create_release
       uses: actions/create-release@v1
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -72,11 +69,12 @@ jobs:
         release_name: shtab ${{ github.ref }} beta
         body_path: _CHANGES.md
         draft: true
-    - uses: actions/upload-release-asset@v1
+    - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+      uses: actions/upload-release-asset@v1
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       with:
         upload_url: ${{ steps.create_release.outputs.upload_url }}
-        asset_path: ${{ steps.collect_assets.outputs.asset_path }}
-        asset_name: ${{ steps.collect_assets.outputs.asset_name }}
+        asset_path: dist/${{ steps.dist.outputs.whl }}
+        asset_name: ${{ steps.dist.outputs.whl }}
         asset_content_type: application/zip
diff --git a/tests/__main__.py b/tests/__main__.py
deleted file mode 100644
index d8566cf..0000000
--- a/tests/__main__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import os
-import sys
-from subprocess import check_call
-
-REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-check_call(
-    (
-        "py.test -v --log-level=debug --cov=shtab"
-        " --cov-report=xml --cov-report=term-missing --durations=0"
-        + (" ".join([""] + sys.argv[1:]))
-    ),
-    shell=True,
-    env=dict(os.environ, PYTHONPATH=REPO_ROOT),
-)
diff --git a/tests/test_shtab.py b/tests/test_shtab.py
index ac956bf..9e431fb 100644
--- a/tests/test_shtab.py
+++ b/tests/test_shtab.py
@@ -1,8 +1,5 @@
 """
 Tests for `shtab`.
-
-Currently runnable via nosetests, e.g.:
-    shtab$ nose tests -d -v
 """
 import logging
 import subprocess