diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6524a3fd7cb85e8093c49e99c381fdbc7c0230c9..ad541ff8bf7c8c67c079f9a26227a2e1b282012b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,17 +20,35 @@ stages: before_script: - pip install -e .[dev] - -python2: - image: python:2.7 +linting: + image: python:3.6 tags: - passoft stage: test - script: tox -e py27 + script: + - flake8 refscrub + - flake8 tests -python3: +python3.6: image: python:3.6 tags: - passoft stage: test - script: tox -e py36 + script: + - python -m unittest + +python3.7: + image: python:3.7 + tags: + - passoft + stage: test + script: + - python -m unittest + +python3.8: + image: python:3.8 + tags: + - passoft + stage: test + script: + - python -m unittest diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3f3c1fa5ce376afedaff562eda6794ec635cf85d..4934a6f12574c5efda7a094f46e96b6073a81ffb 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -45,7 +45,8 @@ articles, and such. Submit Feedback ~~~~~~~~~~~~~~~ -The best way to send feedback is to file an issue at https://git.passcal.nmt.edu/passoft/refscrub/issues. +The best way to send feedback is to file an issue at +https://git.passcal.nmt.edu/passoft/refscrub/issues. If you are proposing a feature: @@ -59,31 +60,31 @@ Get Started! Ready to contribute? Here's how to set up `refscrub` for local development. -1. Cone the `refscrub` repo:: +1. Clone the `refscrub` repo: $ git clone https://git.passcal.nmt.edu/passoft/refscrub.git -3. Install your local copy:: +2. Install your local copy: $ pip install -e .[dev] -4. Create a branch for local development:: +3. Create a branch for local development: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. -5. When you're done making changes, check that your changes pass the - tests:: +4. When you're done making changes, check that your changes pass the + tests: $ python setup.py test -6. Commit your changes and push your branch to GitHub:: +5. Commit your changes and push your branch to GitHub: $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature -7. Submit a merge request through the Gitlab website. +6. Submit a merge request through the Gitlab website. Pull Request Guidelines ----------------------- @@ -94,13 +95,13 @@ Before you submit a merge request, check that it meets these guidelines: 2. If the merge request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. -3. The pull request should work for Python 2.7 +3. The pull request should work for Python 3.[6,7,8] Tips ---- To run a subset of tests:: - $ python -m unittest tests.test_refscrub + $ python -m unittest Deploying --------- @@ -111,4 +112,3 @@ Then run:: $ git push $ git push --tags - diff --git a/HISTORY.rst b/HISTORY.rst index e3a5348c48818e486a566df3f5c20c683861af2c..1f671faf243c045450c96d3156bb5e8633a8a033 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -22,3 +22,15 @@ History * Python 2.7 & 3.6 compatiblity. * Code cleanup to match PEP 8. + +2020.216 (2020-08-04) +------------------ +* Updated to work with Python 3 +* Added unit tests to ensure basic functionalities of refscrub +* Updated list of platform specific dependencies to be installed when + installing refscrub in dev mode (see setup.py) +* Installed and tested refscrub against Python3.[6,7,8] using tox +* Formatted Python code to conform to the PEP8 style guide +* Created conda package for refscrub that can run on Python3.[6,7,8] +* Updated .gitlab-ci.yml to run a linter and unit tests for Python3.[6,7,8] + in GitLab CI pipeline diff --git a/MANIFEST.in b/MANIFEST.in index 965b2dda7db7c49f68857dc3aea9af37e30a745e..2387d6e279b924229fa0c8c485eeeebf49784996 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,7 +5,5 @@ include LICENSE include README.rst recursive-include tests * -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] -recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif +recursive-include docs *.rst conf.py diff --git a/Makefile b/Makefile deleted file mode 100644 index 58e1b94f564fcf4e40c50fbb79bc895531d11b5c..0000000000000000000000000000000000000000 --- a/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -.PHONY: clean clean-test clean-pyc clean-build docs help -.DEFAULT_GOAL := help - -define BROWSER_PYSCRIPT -import os, webbrowser, sys - -try: - from urllib import pathname2url -except: - from urllib.request import pathname2url - -webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) -endef -export BROWSER_PYSCRIPT - -define PRINT_HELP_PYSCRIPT -import re, sys - -for line in sys.stdin: - match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) - if match: - target, help = match.groups() - print("%-20s %s" % (target, help)) -endef -export PRINT_HELP_PYSCRIPT - -BROWSER := python -c "$$BROWSER_PYSCRIPT" - -help: - @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) - -clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts - -clean-build: ## remove build artifacts - rm -fr build/ - rm -fr dist/ - rm -fr .eggs/ - find . -name '*.egg-info' -exec rm -fr {} + - find . -name '*.egg' -exec rm -f {} + - -clean-pyc: ## remove Python file artifacts - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + - -clean-test: ## remove test and coverage artifacts - rm -fr .tox/ - rm -f .coverage - rm -fr htmlcov/ - rm -fr .pytest_cache - -lint: ## check style with flake8 - flake8 refscrub tests - -test: ## run tests quickly with the default Python - python setup.py test - - -test-all: ## run tests on every Python version with tox - tox - -coverage: ## check code coverage quickly with the default Python - coverage run --source refscrub setup.py test - coverage report -m - coverage html - $(BROWSER) htmlcov/index.html - -docs: ## generate Sphinx HTML documentation, including API docs - rm -f docs/refscrub.rst - rm -f docs/modules.rst - sphinx-apidoc -o docs/ refscrub - $(MAKE) -C docs clean - $(MAKE) -C docs html - $(BROWSER) docs/_build/html/index.html - -servedocs: docs ## compile the docs watching for changes - watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . - -release: dist ## package and upload a release - twine upload dist/* - -dist: clean ## builds source and wheel package - python setup.py sdist - python setup.py bdist_wheel - ls -l dist - -install: clean ## install the package to the active Python's site-packages - python setup.py install diff --git a/README.rst b/README.rst index 198b6c62481dfb99f4c334e547613502f350a459..2348a6de6c15da8250ca8834f4772ab3d6e4cdcb 100644 --- a/README.rst +++ b/README.rst @@ -2,23 +2,8 @@ refscrub ======== +* Description: Remove select packets from RT130 data -Remove select packets from RT130 data - +* Usage: efscrub [options] infile1 [ infile2 ... infileN] * Free software: GNU General Public License v3 (GPLv3) - - - -Features --------- - -* TODO - -Credits -------- - -This package was created with Cookiecutter_ and the `passoft/cookiecutter`_ project template. - -.. _Cookiecutter: https://github.com/audreyr/cookiecutter -.. _`passoft/cookiecutter`: https://git.passcal.nmt.edu/passoft/cookiecutter diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 69ed63fa4709740e1860cb60ea4dc51f0b2a7182..32bc711ef9fd6f66c05124320d69d07468e1d277 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,24 +1,31 @@ package: name: refscrub - version: 2011.046 + version: 2020.216 source: - path: .. + path: ../ build: - # If the installation is complex, or different between Unix and Windows, use - # separate bld.bat and build.sh files instead of this key. Add the line - # "skip: True # [py<35]" (for example) to limit to Python 3.5 and newer, or - # "skip: True # [not win]" to limit to Windows. - script: python setup.py install --single-version-externally-managed --record=record.txt + noarch: python + number: 0 + script: {{ PYTHON }} -m pip install . --no-deps -vv + string: py_3 requirements: build: - - python - - setuptools + - python >=3.6 + - pip run: - - python + - python >=3.6 + +test: + source_files: + - tests + commands: + - python -m unittest about: home: https://git.passcal.nmt.edu/passoft/refscrub + license: GPLv3 + license_file: LICENSE summary: Remove select packets from RT130 data diff --git a/docs/conf.py b/docs/conf.py index 3971c64f6a7c8b50ac5b1c4c892c2c8957e83fb9..6e27d97916ea9e851ebd5c9189f1a03534a6cfaa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -47,9 +47,9 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'refscrub' -copyright = u"2018, IRIS PASSCAL" -author = u"IRIS PASSCAL" +project = 'refscrub' +copyright = "2018, IRIS PASSCAL" +author = "IRIS PASSCAL" # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout @@ -129,8 +129,8 @@ latex_elements = { # [howto, manual, or own class]). latex_documents = [ (master_doc, 'refscrub.tex', - u'refscrub Documentation', - u'IRIS PASSCAL', 'manual'), + 'refscrub Documentation', + 'IRIS PASSCAL', 'manual'), ] @@ -140,7 +140,7 @@ latex_documents = [ # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'refscrub', - u'refscrub Documentation', + 'refscrub Documentation', [author], 1) ] @@ -152,7 +152,7 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'refscrub', - u'refscrub Documentation', + 'refscrub Documentation', author, 'refscrub', 'One line description of project.', diff --git a/refscrub/__init__.py b/refscrub/__init__.py index 41aa9e9d143e7571a17e3ad6b5838e2a20f2049f..4ba932794d037a30903282c596d2812e73f8f36d 100644 --- a/refscrub/__init__.py +++ b/refscrub/__init__.py @@ -4,4 +4,4 @@ __author__ = """IRIS PASSCAL""" __email__ = 'software-support@passcal.nmt.edu' -__version__ = '2018.228' +__version__ = '2020.216' diff --git a/refscrub/refscrub.py b/refscrub/refscrub.py index ae66381824d1118f8bea58d5b22d881dc4375399..5f3714d5026dac28a454803375ea3e17e42c2a51 100644 --- a/refscrub/refscrub.py +++ b/refscrub/refscrub.py @@ -13,14 +13,23 @@ August 2018 Updates to work on both Python 2 & 3. Code cleanup to match PEP 8. Cleanup global vars. + +Maeva Pourpoint +August 2020 +Updates to work under Python 3. +Unit tests to ensure basic functionality. +Code cleanup to conform to the PEP8 style guide. +Directory cleanup (remove unused files introduced by Cookiecutter). +Packaged with conda. """ -from __future__ import (print_function, with_statement) -from os.path import join, basename, getsize -import sys + +import argparse import struct +import sys +from io import open +from os.path import basename, getsize, isfile -USAGE = 'usage: %prog [options] infile1 [ infile2 ... infileN]' -PROG_VERSION = '2018.228' +PROG_VERSION = '2020.216' VERBOSE = False EXTRACT = False SUMMARY_FILE = 'scrubsum.txt' @@ -63,21 +72,23 @@ class SNseen(dict): ret.look(year, day, hour) def close_fhs(self): - for sn in self.values(): + for sn in list(self.values()): sn.close_fh() def __str__(self): s = " SN: YR:DAY:HR -- YR:DAY:HR : %12s\n" % 'Good Packets' - for sn in self.values(): + for sn in list(self.values()): s += str(sn) return s class RTPacket: RTstruct = struct.Struct('2c1B1B2B6B2B2B') - # Define the packet types as byte literals, since that is what the above structure will cause the type field - # do be decoded as. Without this, they will not match below. - packet_types = (b'AD', b'CD', b'DS', b'DT', b'EH', b'ET', b'OM', b'SH', b'SC') + # Define the packet types as byte literals, since that is what the above + # structure will cause the type field do be decoded as. Without this, they + # will not match below. + packet_types = (b'AD', b'CD', b'DS', b'DT', + b'EH', b'ET', b'OM', b'SH', b'SC') seen = SNseen() goodpkts = 0 IOErrorCount = 0 @@ -112,11 +123,13 @@ class RTPacket: print(e) def settimestring(self): - self.timestring = "%(year)0.2d:%(day)0.3d:%(hour)0.2d:%(min)0.2d:%(sec)0.2d.%(millisec)0.3d" % self.__dict__ + self.timestring = ("%(year)0.2d:%(day)0.3d:%(hour)0.2d:%(min)0.2d:" + "%(sec)0.2d.%(millisec)0.3d" % self.__dict__) def isvalid(self): """ - Returns True if a valid reftek packet (headers parse well and are valid) + Returns True if a valid reftek packet (headers parse well and are + valid) Also populates the objects attributes SN, time, etc. """ @@ -129,11 +142,12 @@ class RTPacket: self.expnum = int("%0.2X" % tup[2]) self.year = int("%0.2X" % tup[3]) self.sn = "%0.2X%0.2X" % (tup[4], tup[5]) - assert '9001' <= self.sn, "BAD SN" + assert self.sn >= '9001', "BAD SN" assert self.sn <= 'FFFF', "BAD SN" time = "%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X" % tup[6:12] self.day, self.hour, self.min, self.sec, self.millisec = \ - int(time[:3]), int(time[3:5]), int(time[5:7]), int(time[7:9]), int(time[9:]) + int(time[:3]), int(time[3:5]), int( + time[5:7]), int(time[7:9]), int(time[9:]) assert self.day <= 366, "BAD TIME" assert self.hour <= 24, "BAD TIME" assert self.min <= 60, "BAD TIME" @@ -200,8 +214,9 @@ def readfile(infile): infile.seek(-1023, 1) print(summary(infile)) - # commented out as when run with many files as input like a cf dir the closed fh's need to be written to and isn't - # smart enough to be opened RTPacket.seen.close_fhs() + # commented out as when run with many files as input like a cf dir the + # closed fh's need to be written to and isn't smart enough to be opened + # RTPacket.seen.close_fhs() def summary(infile): @@ -209,7 +224,8 @@ def summary(infile): offstring = "%6s: %12d\n" s = offstring % ("OFFSET", infile.tell()) s += offstring % ("OF", FILESIZE) - s += "Good packets: %d = %8.2fMB\n" % (RTPacket.goodpkts, RTPacket.goodpkts / 1024.0) + s += "Good packets: %d = %8.2fMB\n" % ( + RTPacket.goodpkts, RTPacket.goodpkts / 1024.0) s += "IOErrors: %d\n" % RTPacket.IOErrorCount s += str(RTPacket.seen) + '\n' return s @@ -221,39 +237,55 @@ def main(): global PREFIX global FILESIZE summaryfh = None - from optparse import OptionParser - parser = OptionParser(USAGE, version="%prog " + PROG_VERSION) - parser.description = "infile can be a REFTEK file or a raw disk (/dev/disk1) of a CF card." - parser.add_option('-v', '--verbose', dest="VERBOSE", action='store_true', default=False, - help="Prints info about each packet, good or bad. This will increase runtime.") - parser.add_option('-e', '--extract', dest='EXTRACT', action='store_true', default=False, - help='Writes good packets to files named infile.SNXX.scrub.ref OR PREFIX.SNXX.scrub.ref, ' - ' if given, for each Serial Number found. If output file exists it will append. Be careful ' - 'not to duplicate data by running more than once on the same file in the same dir.') - parser.add_option('-p', '--prefix', dest='PREFIX', - help='Prefix of output filename. Defaults to inputfilename') - parser.add_option('-s', '--savesum', dest='SUMMARY', action='store_true', default=False, - help='Appends summary to %s' % SUMMARY_FILE) - options, args = parser.parse_args() - VERBOSE = options.VERBOSE - EXTRACT = options.EXTRACT - PREFIX = options.PREFIX - if options.SUMMARY: + parser = argparse.ArgumentParser(prog="refscrub", + usage="%(prog)s [options] infile1 " + "[ infile2 ... infileN]") + parser.add_argument('infile', nargs='*', metavar='infile', + help="infile can be a REFTEK file or a raw disk " + "(/dev/disk1) of a CF card.") + parser.add_argument('--version', action='version', + version="%(prog)s " + PROG_VERSION) + parser.add_argument('-v', '--verbose', dest="VERBOSE", action='store_true', + default=False, help="Prints info about each packet, " + "good or bad. This will increase runtime.") + parser.add_argument('-e', '--extract', dest='EXTRACT', action='store_true', + default=False, help="Writes good packets to files " + "named infile.SNXX.scrub.ref OR " + "PREFIX.SNXX.scrub.ref, if given, for each Serial " + "Number found. If output file exists it will append. " + "Be careful not to duplicate data by running more " + "than once on the same file in the same dir.") + parser.add_argument('-p', '--prefix', dest='PREFIX', + help="Prefix of output filename. Defaults to input" + "filename") + parser.add_argument('-s', '--savesum', dest='SUMMARY', action='store_true', + default=False, help='Appends summary to %s' + % SUMMARY_FILE) + args = parser.parse_args() + if not args.infile: + parser.print_help() + sys.exit(1) + VERBOSE = args.VERBOSE + EXTRACT = args.EXTRACT + PREFIX = args.PREFIX + if args.SUMMARY: summaryfh = open(SUMMARY_FILE, 'a') - for infilename in args: + for infilename in args.infile: print("Processing: %s" % infilename) if not PREFIX: PREFIX = basename(infilename) print("Using prefix %s" % PREFIX) FILESIZE = getsize(infilename) - # Must open the file in binary mode or else we will have unicode issues when reading. + # Must open the file in binary mode or else we will have unicode issues + # when reading. with open(infilename, "rb") as infile: readfile(infile) - if options.SUMMARY: + if args.SUMMARY: summaryfh.write(infilename + ', ' + PREFIX + '\n') summaryfh.write(summary(infile)) - print("----------------------------------------") + if isfile(SUMMARY_FILE): + summaryfh.close() if __name__ == '__main__': diff --git a/setup.cfg b/setup.cfg index 371beb7b759af77d8f94aa900d751bf79f767bd9..3e16c9aa4de92883ce48eebabea3688a4ead6f10 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,22 +1,5 @@ -[bumpversion] -current_version = 2018.228 -commit = True -tag = True - -[bumpversion:file:setup.py] -search = version='{current_version}' -replace = version='{new_version}' - -[bumpversion:file:refscrub/__init__.py] -search = __version__ = '{current_version}' -replace = __version__ = '{new_version}' - -[bdist_wheel] -universal = 1 - [flake8] exclude = docs [aliases] # Define setup.py command aliases here - diff --git a/setup.py b/setup.py index 288cc8fc627f0e09fb47381efc903de58d87ed87..f46c4df25595f43b299df6f696bf55d39fd7da58 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,13 @@ """The setup script.""" +from io import open from setuptools import setup, find_packages -with open('README.rst') as readme_file: +with open('README.rst', 'rt') as readme_file: readme = readme_file.read() -with open('HISTORY.rst') as history_file: +with open('HISTORY.rst', 'rt') as history_file: history = history_file.read() @@ -21,6 +22,8 @@ setup( 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Natural Language :: English', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', ], description="Remove select packets from RT130 data", entry_points={ @@ -29,18 +32,11 @@ setup( ], }, install_requires=[], - setup_requires = [], + setup_requires=[], extras_require={ 'dev': [ - 'pip', - 'bumpversion', - 'wheel', - 'watchdog', 'flake8', 'tox', - 'coverage', - 'Sphinx', - 'twine', ] }, license="GNU General Public License v3", @@ -49,8 +45,7 @@ setup( keywords='refscrub', name='refscrub', packages=find_packages(include=['refscrub']), - test_suite='tests', url='https://git.passcal.nmt.edu/passoft/refscrub', - version='2018.228', + version='2020.216', zip_safe=False, ) diff --git a/tests/test_data/020000005_0036EE80 b/tests/test_data/020000005_0036EE80 new file mode 100755 index 0000000000000000000000000000000000000000..39a43f34b818072f4a1ddff7b0f8b2997994d8fa Binary files /dev/null and b/tests/test_data/020000005_0036EE80 differ diff --git a/tests/test_data/scrubsum_test.txt b/tests/test_data/scrubsum_test.txt new file mode 100644 index 0000000000000000000000000000000000000000..866f43400361faf76981375916a92463b2493729 --- /dev/null +++ b/tests/test_data/scrubsum_test.txt @@ -0,0 +1,8 @@ +tests/test_data/020000005_0036EE80, 020000005_0036EE80 +OFFSET: 2478080 + OF: 2478080 +Good packets: 2420 = 2.36MB +IOErrors: 0 + SN: YR:DAY:HR -- YR:DAY:HR : Good Packets + 91F5: 16:100:02 -- 16:100:02 : 2420 + diff --git a/tests/test_refscrub.py b/tests/test_refscrub.py index 6af0b2dc456bbf8658c58d60118d4d3fa777b326..85a564d33c4a67c5456bc74f76764cd6c7b4b273 100644 --- a/tests/test_refscrub.py +++ b/tests/test_refscrub.py @@ -3,26 +3,47 @@ """Tests for `refscrub` package.""" +import argparse +import io +import os import unittest -import sys -try: - import refscrub -except ImportError: - pass +from unittest.mock import patch +from refscrub.refscrub import main -class TestRefscrub(unittest.TestCase): - """Tests for `refscrub` package.""" +TEST_DATA = "tests/test_data/020000005_0036EE80" +TEST_SUMMARY = "tests/test_data/scrubsum_test.txt" - def setUp(self): - """Set up test fixtures, if any.""" +if os.path.isfile('scrubsum.txt'): + os.remove('scrubsum.txt') - def tearDown(self): - """Tear down test fixtures, if any.""" - def test_import(self): - if 'refscrub' in sys.modules: - self.assert_(True, "refscrub loaded") - else: - self.fail() +class TestRefscrub(unittest.TestCase): + """Tests for `refscrub` package.""" + @patch('refscrub.refscrub.sys.exit', autospec=True) + @patch('argparse.ArgumentParser.parse_args', autospec=True) + def test_refscrub(self, mock_parser, mock_exit): + """Test basic functionality of refscrub""" + mock_parser.return_value = argparse.Namespace(EXTRACT=False, + PREFIX=None, + SUMMARY=False, + VERBOSE=False, + infile=[]) + main() + self.assertTrue(mock_exit.called, "sys.exit(1) never called - Failed " + "to exercise refscrub") + + mock_parser.return_value = argparse.Namespace(EXTRACT=False, + PREFIX=None, + SUMMARY=True, + VERBOSE=False, + infile=[TEST_DATA]) + main() + self.assertTrue(os.path.isfile('scrubsum.txt'), "Error! Summary file " + "not created.") + with io.open("scrubsum.txt", "rt") as f_out: + out = f_out.readlines() + with io.open(TEST_SUMMARY, "rt") as f_test: + test = f_test.readlines() + self.assertEqual(out, test) diff --git a/tox.ini b/tox.ini index 3e04f0d91f5323f3904fb517bf5b8922dbd42e0d..9ff41874c14ef7ed806c09586cece3179e66d10e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,19 +1,11 @@ [tox] -envlist = py27, py36 flake8 - -[travis] -python = - 2.7: py27 - 3.6: py36 +envlist = py36, py37, py38, flake8 [testenv:flake8] basepython = python deps = flake8 commands = flake8 refscrub + flake8 tests [testenv] -setenv = - PYTHONPATH = {toxinidir} -commands=python setup.py test - - +commands = python -m unittest