Repository: tqdm/tqdm (language: python)

Issue:
docs: update the simplified `make alltests` instructions to include reference to installing coverage
The "Alternative unit tests with pytest" instructions in CONTRIBUTING.md no longer work after switching from [nose -> pytest](https://github.com/tqdm/tqdm/pull/1052). You need to also install `coverage` and `pytest-cov` in order to run `make alltests`

```
ERROR: usage: pytest [options] [file_or_dir] [file_or_dir] [...]                                                                                           
pytest: error: unrecognized arguments: --cov=tqdm --cov-fail-under=80                                                                                      
```


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index d36c9a0ed..1e4432333 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -25,24 +25,6 @@ jobs:
     - run: tox
       env:
         TOXENV: ${{ matrix.TOXENV }}
-  lint:
-    if: github.event_name != 'pull_request' || github.head_ref != 'devel'
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
-      with:
-        python-version: '3.x'
-    - uses: reviewdog/action-setup@v1
-    - run: pip install -U flake8
-    - if: github.event_name != 'schedule'
-      name: flake8
-      run: |
-        set -o pipefail
-        flake8 -j8 --count --statistics . | \
-          reviewdog -f=pep8 -name=flake8 -tee -reporter=github-check -filter-mode nofilter
-      env:
-        REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
   asvfull:
     if: (github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')) || github.event_name == 'schedule'
     name: Benchmark (Full)
diff --git a/.gitignore b/.gitignore
index b45f8695b..26e230848 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,42 +1,24 @@
 *.py[cod]
-
-# C extensions
 *.so
+__pycache__/
+.ipynb_checkpoints/
 
 # Packages
 /tqdm/_dist_ver.py
 /.eggs/
-/tqdm.egg-info
+/*.egg*/
 /build/
 /dist/
 /snapcraft.yaml
-/tqdm_*_amd64.snap
+/tqdm_*.snap
 /.dockerignore
 
 # Unit test / coverage reports
-.tox/
-.coverage
-.coverage.*
-__pycache__/
-.pytest_cache/
-
-# Translations
-*.mo
-
-# Mr Developer
-.mr.developer.cfg
-.project
-.pydevproject
-
-# PyCharm
-.idea
-
-# IPython
-.ipynb_checkpoints
-
-# asv
-.asv/
-benchmarks/*.py[co]
+/.tox/
+/.coverage*
+/coverage.xml
+/.pytest_cache/
+/.asv/
 
 # Sumbodules
 /wiki/
diff --git a/.meta/.readme.rst b/.meta/.readme.rst
index 88d204ad4..92307e4db 100644
--- a/.meta/.readme.rst
+++ b/.meta/.readme.rst
@@ -1121,11 +1121,11 @@ Stephen Larroque     `lrq3000 <https://github.com/lrq3000>`__                 ~1
 Martin Zugnoni       `martinzugnoni <https://github.com/martinzugnoni>`__     ~3%
 Guangshuo Chen       `chengs <https://github.com/chengs>`__                   ~1%
 Kyle Altendorf       `altendky <https://github.com/altendky>`__               <1%
-Matthew Stevens      `mjstevens777 <https://github.com/mjstevens777>`__       <1%
-Jona Haag            `<https://github.com/>`__                                <1%
 Hadrien Mary         `hadim <https://github.com/hadim>`__                     <1%  team member
-Daniel Panteleit     `<https://github.com/>`__                                <1%
+Matthew Stevens      `mjstevens777 <https://github.com/mjstevens777>`__       <1%
 Ivan Ivanov          `obiwanus <https://github.com/obiwanus>`__               <1%
+Daniel Panteleit     `danielpanteleit <https://github.com/danielpanteleit>`__ <1%
+Jona Haag            `jonashaag <https://github.com/jonashaag>`__             <1%
 James E. King III    `jeking3 <https://github.com/jeking3>`__                 <1%
 Noam Yorav-Raphael   `noamraph <https://github.com/noamraph>`__               <1%  original author
 Mikhail Korobov      `kmike <https://github.com/kmike>`__                     <1%  team member
diff --git a/.meta/mkcompletion.py b/.meta/mkcompletion.py
index 67f56ba76..8c56967b7 100644
--- a/.meta/mkcompletion.py
+++ b/.meta/mkcompletion.py
@@ -2,10 +2,11 @@
 Auto-generate tqdm/completion.sh from docstrings.
 """
 from __future__ import print_function
-from io import open as io_open
-from os import path
+
 import re
 import sys
+from io import open as io_open
+from os import path
 
 sys.path.insert(0, path.dirname(path.dirname(__file__)))
 import tqdm  # NOQA
diff --git a/.meta/mkdocs.py b/.meta/mkdocs.py
index e62c6d42b..4bb1a93dd 100644
--- a/.meta/mkdocs.py
+++ b/.meta/mkdocs.py
@@ -2,9 +2,10 @@
 Auto-generate README.rst from .meta/.readme.rst and docstrings.
 """
 from __future__ import print_function
+
+import sys
 from io import open as io_open
 from os import path
-import sys
 from textwrap import dedent
 
 sys.path.insert(0, path.dirname(path.dirname(__file__)))
diff --git a/.meta/mksnap.py b/.meta/mksnap.py
index f3154201a..e06c17613 100644
--- a/.meta/mksnap.py
+++ b/.meta/mksnap.py
@@ -2,10 +2,10 @@
 """
 Auto-generate snapcraft.yaml.
 """
+import sys
 from io import open as io_open
 from os import path
 from subprocess import check_output
-import sys
 
 sys.path.insert(1, path.dirname(path.dirname(__file__)))
 import tqdm  # NOQA
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 000000000..71748efa5
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,58 @@
+default_language_version:
+  python: python3
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+  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
+    exclude: ^README.rst$
+- repo: local
+  hooks:
+  - id: todo
+    name: Check TODO
+    language: pygrep
+    entry: WIP
+    types: [text]
+    exclude: ^(.pre-commit-config.yaml|.github/workflows/test.yml)$
+    args: [-i]
+  - id: metadata
+    name: Check metadata
+    language: system
+    entry: make testsetup
+    pass_filenames: false
+  - id: pytest
+    name: pytest quick
+    entry: pytest
+    args: ['-qq', '--durations=1', '-k=not slow']
+    pass_filenames: false
+    types: [python]
+    language: python
+    additional_dependencies:
+    - numpy
+    - pandas
+    - pytest-timeout
+    - pytest-asyncio
+- 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://github.com/PyCQA/isort
+  rev: 5.7.0
+  hooks:
+  - id: isort
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 17b4a3179..568df615f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -103,7 +103,7 @@ that each distribution has an alias to call the Python interpreter:
 
 Alternatively, use `pytest` to run the tests just for the current Python version:
 
-- install `pytest` and `flake8`
+- install `pytest`, `coverage`, `pytest-cov` and `flake8`
 - run the following command:
 
 ```
@@ -123,8 +123,8 @@ Also consider `pip install`ing development utilities:
 
 ## Pre-commit Hook
 
-It's probably a good idea to add `[python setup.py] make pre-commit` to
-`.git/hooks/pre-commit` for convenient local sanity-checking.
+It's probably a good idea to run `pre-commit install` for convenient local
+sanity-checking.
 
 
 ## Semantic Versioning
diff --git a/Makefile b/Makefile
index 249e9b041..ee13c560a 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,6 @@
 	testtimer
 	distclean
 	coverclean
-	pre-commit
 	prebuildclean
 	clean
 	toxclean
@@ -62,11 +61,11 @@ testsetup:
 
 testcoverage:
 	@make coverclean
-	pytest -k "not tests_perf" --cov=tqdm --cov-fail-under=80
+	pytest -k "not perf" --cov=tqdm --cov-fail-under=80
 
 testperf:
 	# do not use coverage (which is extremely slow)
-	pytest -k tests_perf
+	pytest -k perf
 
 testtimer:
 	pytest
@@ -112,11 +111,6 @@ distclean:
 	@+make coverclean
 	@+make prebuildclean
 	@+make clean
-pre-commit:
-	# quick sanity checks
-	@make --no-print-directory testsetup
-	flake8 -j 8 --count --statistics setup.py .meta/ tqdm/ tests/ examples/
-	pytest -qq --durations=1 -k "basic_overhead or not (perf or keras or pandas or monitoring)"
 prebuildclean:
 	@+python -c "import shutil; shutil.rmtree('build', True)"
 	@+python -c "import shutil; shutil.rmtree('dist', True)"
diff --git a/README.rst b/README.rst
index ed9d55d1c..e48ece77b 100644
--- a/README.rst
+++ b/README.rst
@@ -1338,11 +1338,11 @@ Stephen Larroque     `lrq3000 <https://github.com/lrq3000>`__                 ~1
 Martin Zugnoni       `martinzugnoni <https://github.com/martinzugnoni>`__     ~3%
 Guangshuo Chen       `chengs <https://github.com/chengs>`__                   ~1%
 Kyle Altendorf       `altendky <https://github.com/altendky>`__               <1%
-Matthew Stevens      `mjstevens777 <https://github.com/mjstevens777>`__       <1%
-Jona Haag            `<https://github.com/>`__                                <1%
 Hadrien Mary         `hadim <https://github.com/hadim>`__                     <1%  team member
-Daniel Panteleit     `<https://github.com/>`__                                <1%
+Matthew Stevens      `mjstevens777 <https://github.com/mjstevens777>`__       <1%
 Ivan Ivanov          `obiwanus <https://github.com/obiwanus>`__               <1%
+Daniel Panteleit     `danielpanteleit <https://github.com/danielpanteleit>`__ <1%
+Jona Haag            `jonashaag <https://github.com/jonashaag>`__             <1%
 James E. King III    `jeking3 <https://github.com/jeking3>`__                 <1%
 Noam Yorav-Raphael   `noamraph <https://github.com/noamraph>`__               <1%  original author
 Mikhail Korobov      `kmike <https://github.com/kmike>`__                     <1%  team member
diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py
index b3e419c94..5698600da 100644
--- a/benchmarks/benchmarks.py
+++ b/benchmarks/benchmarks.py
@@ -1,6 +1,7 @@
 # Write the benchmarking functions here.
 # See "Writing benchmarks" in the asv docs for more information.
 from __future__ import division
+
 from functools import partial
 
 
diff --git a/environment.yml b/environment.yml
index 2ead4b1f5..2c74a3cf1 100644
--- a/environment.yml
+++ b/environment.yml
@@ -12,13 +12,17 @@ dependencies:
 - setuptools_scm
 - toml
 # test env managers
+- pre-commit
 - tox
 - asv
 # tests (native)
 - pytest
 - pytest-cov
+- pytest-timeout
 - pytest-asyncio  # [py>=3.7]
 - flake8
+- flake8-bugbear
+- flake8-comprehensions
 - coverage
 # extras
 - matplotlib         # gui
@@ -32,3 +36,5 @@ dependencies:
 - pip:
   - py-make >=0.1.0  # `setup.py make/pymake`
   - pydoc-markdown   # `cd docs && pymake`
+  - flake8-debugger       # flake8
+  - flake8-string-format  # flake8
diff --git a/examples/7zx.py b/examples/7zx.py
index 9086eb48f..892dcf764 100644
--- a/examples/7zx.py
+++ b/examples/7zx.py
@@ -19,14 +19,18 @@
   -d, --debug-trace      Print lots of debugging information (-D NOTSET)
 """
 from __future__ import print_function
-from argopt import argopt
+
+import io
 import logging
-import subprocess
+import os
+import pty
 import re
+import subprocess
+
+from argopt import argopt
+
 from tqdm import tqdm
-import pty
-import os
-import io
+
 __author__ = "Casper da Costa-Luis <casper.dcl@physics.org>"
 __licence__ = "MPLv2.0"
 __version__ = "0.2.2"
@@ -57,8 +61,8 @@ def main():
         for s in range(2):  # size|compressed totals
             totals_s = sum(map(int, (inf[s] for inf in finfo[:-1])))
             if totals_s != totals[s]:
-                log.warn("%s: individual total %d != 7z total %d" %
-                         (fn, totals_s, totals[s]))
+                log.warn("%s: individual total %d != 7z total %d",
+                         fn, totals_s, totals[s])
         fcomp = {n: int(c if args.compressed else u) for (u, c, n) in finfo[:-1]}
         # log.debug(fcomp)
         # zips  : {'zipname' : {'filename' : int(size)}}
@@ -68,7 +72,7 @@ def main():
     cmd7zx = ["7z", "x", "-bd"]
     if args.yes:
         cmd7zx += ["-y"]
-    log.info("Extracting from {:d} file(s)".format(len(zips)))
+    log.info("Extracting from %d file(s)", len(zips))
     with tqdm(total=sum(sum(fcomp.values()) for fcomp in zips.values()),
               unit="B", unit_scale=True) as tall:
         for fn, fcomp in zips.items():
@@ -105,7 +109,7 @@ def main():
                                     if not args.silent:
                                         t.write(t.format_interval(
                                             t.start_t - tall.start_t) + ' ' +
-                                            ln.lstrip("Processing archive: "))
+                                            ln.replace("Processing archive: ", ""))
                                 else:
                                     t.write(ln)
             ex.wait()
diff --git a/examples/async_coroutines.py b/examples/async_coroutines.py
index 00f074e2d..40f4f249d 100644
--- a/examples/async_coroutines.py
+++ b/examples/async_coroutines.py
@@ -2,6 +2,7 @@
 Asynchronous examples using `asyncio`, `async` and `await` on `python>=3.7`.
 """
 import asyncio
+
 from tqdm.asyncio import tqdm, trange
 
 
diff --git a/examples/coroutine_pipe.py b/examples/coroutine_pipe.py
index 490037f1a..deb498ad9 100644
--- a/examples/coroutine_pipe.py
+++ b/examples/coroutine_pipe.py
@@ -3,6 +3,7 @@
 Not to be confused with `asyncio.coroutine`.
 """
 from functools import wraps
+
 from tqdm.auto import tqdm
 
 
diff --git a/examples/pandas_progress_apply.py b/examples/pandas_progress_apply.py
index 1b0fc2309..4fc8f6b16 100644
--- a/examples/pandas_progress_apply.py
+++ b/examples/pandas_progress_apply.py
@@ -1,5 +1,6 @@
-import pandas as pd
 import numpy as np
+import pandas as pd
+
 from tqdm.auto import tqdm
 
 df = pd.DataFrame(np.random.randint(0, 100, (100000, 6)))
diff --git a/examples/parallel_bars.py b/examples/parallel_bars.py
index 1cf78b217..2e8a9c3b8 100644
--- a/examples/parallel_bars.py
+++ b/examples/parallel_bars.py
@@ -1,11 +1,12 @@
 from __future__ import print_function
+
+import sys
 from concurrent.futures import ThreadPoolExecutor
 from functools import partial
 from multiprocessing import Pool, RLock, freeze_support
 from random import random
 from threading import RLock as TRLock
 from time import sleep
-import sys
 
 from tqdm.auto import tqdm, trange
 from tqdm.contrib.concurrent import process_map, thread_map
@@ -17,7 +18,7 @@
 def progresser(n, auto_position=True, write_safe=False, blocking=True, progress=False):
     interval = random() * 0.002 / (NUM_SUBITERS - n + 2)
     total = 5000
-    text = "#{}, est. {:<04.2}s".format(n, interval * total)
+    text = "#{0}, est. {1:<04.2}s".format(n, interval * total)
     for _ in trange(total, desc=text, disable=not progress,
                     lock_args=None if blocking else (False,),
                     position=None if auto_position else n):
diff --git a/examples/redirect_print.py b/examples/redirect_print.py
index a33e23505..0f9721ee6 100644
--- a/examples/redirect_print.py
+++ b/examples/redirect_print.py
@@ -11,9 +11,11 @@
 A reusable canonical example is given below:
 """
 from __future__ import print_function
-from time import sleep
+
 import contextlib
 import sys
+from time import sleep
+
 from tqdm import tqdm
 from tqdm.contrib import DummyTqdmFile
 
diff --git a/examples/simple_examples.py b/examples/simple_examples.py
index 9d54b92f1..f3401d357 100644
--- a/examples/simple_examples.py
+++ b/examples/simple_examples.py
@@ -47,13 +47,14 @@
     pass
 """
 
+import re
 from time import sleep
 from timeit import timeit
-import re
 
 # Simple demo
 from tqdm import trange
-for i in trange(16, leave=True):
+
+for _ in trange(16, leave=True):
     sleep(0.1)
 
 # Profiling/overhead tests
diff --git a/examples/tqdm_requests.py b/examples/tqdm_requests.py
index ef4483502..5d03594cc 100644
--- a/examples/tqdm_requests.py
+++ b/examples/tqdm_requests.py
@@ -16,10 +16,10 @@
 
 from os import devnull
 
-from docopt import docopt
 import requests
-from tqdm.auto import tqdm
+from docopt import docopt
 
+from tqdm.auto import tqdm
 
 opts = docopt(__doc__)
 
diff --git a/examples/tqdm_wget.py b/examples/tqdm_wget.py
index 4a7ee946c..695e5e5a1 100644
--- a/examples/tqdm_wget.py
+++ b/examples/tqdm_wget.py
@@ -20,10 +20,11 @@
     The local file path in which to save the url [default: /dev/null].
 """
 
+import urllib
 from os import devnull
 
 from docopt import docopt
-import urllib
+
 from tqdm.auto import tqdm
 
 
diff --git a/examples/wrapping_generators.py b/examples/wrapping_generators.py
index 0ec068956..65c85bfd8 100644
--- a/examples/wrapping_generators.py
+++ b/examples/wrapping_generators.py
@@ -1,6 +1,7 @@
-from tqdm.contrib import tenumerate, tzip, tmap
 import numpy as np
 
+from tqdm.contrib import tenumerate, tmap, tzip
+
 for _ in tenumerate(range(int(1e6)), desc="builtin enumerate"):
     pass
 
diff --git a/setup.cfg b/setup.cfg
index 65072d010..b0b437c63 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,45 +1,23 @@
-[bdist_wheel]
-universal = 1
-
-[flake8]
-ignore = W503,W504,E722
-max_line_length = 88
-exclude = .asv,.eggs,.tox,.ipynb_checkpoints,build,dist,.git,__pycache__
-
-[yapf]
-coalesce_brackets=True
-column_limit=88
-each_dict_entry_on_separate_line=False
-i18n_comment=NOQA
-space_between_ending_comma_and_closing_bracket=False
-split_before_named_assigns=False
-split_before_closing_bracket=False
-
-[tool:pytest]
-python_files=tests_*.py
-testpaths=tests
-addopts=-v --tb=short -rxs --durations=0 --durations-min=0.1
-
 [metadata]
-name = tqdm
-url = https://github.com/tqdm/tqdm
-project_urls =
-    Changelog = https://tqdm.github.io/releases
-    Documentation = https://github.com/tqdm/tqdm#tqdm
-    Documentation (dev) = https://tqdm.github.io/docs/tqdm
-    Wiki = https://github.com/tqdm/tqdm/wiki
-maintainer = tqdm developers
-maintainer_email = python.tqdm@gmail.com
-license = MPLv2.0, MIT Licences
-license_file = LICENCE
-description = Fast, Extensible Progress Meter
-long_description = file: README.rst
-long_description_content_type = text/x-rst
-keywords = progressbar, progressmeter, progress, bar, meter, rate, eta, console, terminal, time
-platforms = any
-provides = tqdm
+name=tqdm
+url=https://github.com/tqdm/tqdm
+project_urls=
+    Changelog=https://tqdm.github.io/releases
+    Documentation=https://github.com/tqdm/tqdm#tqdm
+    Documentation (dev)=https://tqdm.github.io/docs/tqdm
+    Wiki=https://github.com/tqdm/tqdm/wiki
+maintainer=tqdm developers
+maintainer_email=python.tqdm@gmail.com
+license=MPLv2.0, MIT Licences
+license_file=LICENCE
+description=Fast, Extensible Progress Meter
+long_description=file: README.rst
+long_description_content_type=text/x-rst
+keywords=progressbar, progressmeter, progress, bar, meter, rate, eta, console, terminal, time
+platforms=any
+provides=tqdm
 # Trove classifiers (https://pypi.org/pypi?%3Aaction=list_classifiers)
-classifiers =
+classifiers=
     Development Status :: 5 - Production/Stable
     Environment :: Console
     Environment :: MacOS X
@@ -96,16 +74,47 @@ classifiers =
     Topic :: Terminals
     Topic :: Utilities
 [options]
-setup_requires = setuptools>=42; setuptools_scm[toml]>=3.4
-python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
-tests_require = pytest; flake8; coverage
-include_package_data = True
-packages = find:
+setup_requires=setuptools>=42; setuptools_scm[toml]>=3.4
+python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+tests_require=pytest; flake8; coverage
+include_package_data=True
+packages=find:
 [options.extras_require]
 dev=py-make>=0.1.0; twine; wheel
 telegram=requests
 [options.entry_points]
-console_scripts =
-    tqdm = tqdm.cli:main
+console_scripts=
+    tqdm=tqdm.cli:main
 [options.packages.find]
-exclude = benchmarks, tests
+exclude=benchmarks, tests
+
+[bdist_wheel]
+universal=1
+
+[flake8]
+max_line_length=88
+exclude=.asv,.eggs,.tox,.ipynb_checkpoints,build,dist,.git,__pycache__
+
+[yapf]
+coalesce_brackets=True
+column_limit=88
+each_dict_entry_on_separate_line=False
+i18n_comment=NOQA
+space_between_ending_comma_and_closing_bracket=False
+split_before_named_assigns=False
+split_before_closing_bracket=False
+
+[isort]
+line_length=88
+multi_line_output=4
+known_first_party=tqdm,tests
+
+[tool:pytest]
+timeout=30
+log_level=INFO
+markers=
+    asyncio
+    slow
+python_files=tests_*.py
+testpaths=tests
+addopts=-v --tb=short -rxs -W=error --durations=0 --durations-min=0.1
diff --git a/setup.py b/setup.py
index 65136e94c..ba21964b7 100755
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,10 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from setuptools import setup
 import os
 import sys
 
+from setuptools import setup
+
 src_dir = os.path.abspath(os.path.dirname(__file__))
 if sys.argv[1].lower().strip() == 'make':  # exec Makefile commands
     import pymake
diff --git a/tox.ini b/tox.ini
index 31f8b7df3..d27c3a879 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,8 +5,8 @@
 
 [tox]
 # deprecation warning: py{34}
-envlist = py{27,34,35,36,37,38,39,py2,py3}{,-tf}{,-keras}, perf, flake8, setup.py
-isolated_build = True
+envlist=py{27,34,35,36,37,38,39,py2,py3}{,-tf}{,-keras}, perf, flake8, setup.py
+isolated_build=True
 
 [coverage]
 commands=
@@ -15,52 +15,60 @@ commands=
     - codacy report -r coverage.xml
 
 [extra]
-deps =
+deps=
     pytest
     pytest-cov
+    pytest-timeout
     py{37,38,39}: pytest-asyncio
     coverage
     coveralls
     codecov
-commands =
-    pytest --cov=tqdm --cov-report=xml --cov-report=term -k "not tests_perf"
+commands=
+    pytest --cov=tqdm --cov-report=xml --cov-report=term -k "not perf"
     {[coverage]commands}
-allowlist_externals = codacy
+allowlist_externals=codacy
 
 [testenv]
-passenv = CI TOXENV CODECOV_* COVERALLS_* CODACY_* HOME
-deps =
+passenv=CI TOXENV CODECOV_* COVERALLS_* CODACY_* HOME
+deps=
     {[extra]deps}
     cython
     numpy
     pandas
     tf: tensorflow
     keras: keras
-commands = {[extra]commands}
-allowlist_externals = {[extra]allowlist_externals}
+commands={[extra]commands}
+allowlist_externals={[extra]allowlist_externals}
 
 [testenv:py{27,py2}{,-tf}{,-keras}]
-commands =
-    pytest --cov=tqdm --cov-report=xml --cov-report=term -k "not tests_perf" -o addopts= -v --tb=short -rxs --durations=10
+commands=
+    pytest --cov=tqdm --cov-report=xml --cov-report=term -k "not perf" -o addopts= -v --tb=short -rxs -W=error --durations=10
     {[coverage]commands}
 
 # no cython/numpy/pandas
 [testenv:py{34,py2,py3}]
-deps = {[extra]deps}
+deps={[extra]deps}
 
 [testenv:perf]
-deps = pytest
-commands = pytest -k tests_perf
+deps=
+    pytest
+    pytest-timeout
+commands=pytest -k perf
 
 [testenv:flake8]
-deps = flake8
-commands = flake8 -j 8 --count --statistics .
+deps=
+    flake8
+    flake8-bugbear
+    flake8-comprehensions
+    flake8-debugger
+    flake8-string-format
+commands=flake8 -j 8 --count --statistics .
 
 [testenv:setup.py]
-deps =
+deps=
     docutils
     pygments
     py-make>=0.1.0
-commands =
+commands=
     {envpython} setup.py check --restructuredtext --metadata --strict
     {envpython} setup.py make none
diff --git a/tqdm/__init__.py b/tqdm/__init__.py
index 70601f417..a021d16e9 100644
--- a/tqdm/__init__.py
+++ b/tqdm/__init__.py
@@ -1,13 +1,13 @@
-from .std import tqdm, trange
-from .gui import tqdm as tqdm_gui  # TODO: remove in v5.0.0
-from .gui import trange as tgrange  # TODO: remove in v5.0.0
+from ._monitor import TMonitor, TqdmSynchronisationWarning
 from ._tqdm_pandas import tqdm_pandas
 from .cli import main  # TODO: remove in v5.0.0
-from ._monitor import TMonitor, TqdmSynchronisationWarning
+from .gui import tqdm as tqdm_gui  # TODO: remove in v5.0.0
+from .gui import trange as tgrange  # TODO: remove in v5.0.0
+from .std import (
+    TqdmDeprecationWarning, TqdmExperimentalWarning, TqdmKeyError, TqdmMonitorWarning,
+    TqdmTypeError, TqdmWarning, tqdm, trange)
 from .version import __version__
-from .std import TqdmTypeError, TqdmKeyError, TqdmWarning, \
-    TqdmDeprecationWarning, TqdmExperimentalWarning, \
-    TqdmMonitorWarning
+
 __all__ = ['tqdm', 'tqdm_gui', 'trange', 'tgrange', 'tqdm_pandas',
            'tqdm_notebook', 'tnrange', 'main', 'TMonitor',
            'TqdmTypeError', 'TqdmKeyError',
@@ -19,8 +19,9 @@
 
 def tqdm_notebook(*args, **kwargs):  # pragma: no cover
     """See tqdm.notebook.tqdm for full documentation"""
-    from .notebook import tqdm as _tqdm_notebook
     from warnings import warn
+
+    from .notebook import tqdm as _tqdm_notebook
     warn("This function will be removed in tqdm==5.0.0\n"
          "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`",
          TqdmDeprecationWarning, stacklevel=2)
@@ -32,8 +33,9 @@ def tnrange(*args, **kwargs):  # pragma: no cover
     A shortcut for `tqdm.notebook.tqdm(xrange(*args), **kwargs)`.
     On Python3+, `range` is used instead of `xrange`.
     """
-    from .notebook import trange as _tnrange
     from warnings import warn
+
+    from .notebook import trange as _tnrange
     warn("Please use `tqdm.notebook.trange` instead of `tqdm.tnrange`",
          TqdmDeprecationWarning, stacklevel=2)
     return _tnrange(*args, **kwargs)
diff --git a/tqdm/__main__.py b/tqdm/__main__.py
index 130bc6375..4e28416e1 100644
--- a/tqdm/__main__.py
+++ b/tqdm/__main__.py
@@ -1,2 +1,3 @@
 from .cli import main
+
 main()
diff --git a/tqdm/_main.py b/tqdm/_main.py
index 07b6730b1..04fdeeff1 100644
--- a/tqdm/_main.py
+++ b/tqdm/_main.py
@@ -1,7 +1,9 @@
+from warnings import warn
+
 from .cli import *  # NOQA
 from .cli import __all__  # NOQA
 from .std import TqdmDeprecationWarning
-from warnings import warn
+
 warn("This function will be removed in tqdm==5.0.0\n"
      "Please use `tqdm.cli.*` instead of `tqdm._main.*`",
      TqdmDeprecationWarning, stacklevel=2)
diff --git a/tqdm/_monitor.py b/tqdm/_monitor.py
index d7f1c3c3d..f8443bca3 100644
--- a/tqdm/_monitor.py
+++ b/tqdm/_monitor.py
@@ -1,7 +1,8 @@
+import atexit
 from threading import Event, Thread, current_thread
 from time import time
 from warnings import warn
-import atexit
+
 __all__ = ["TMonitor", "TqdmSynchronisationWarning"]
 
 
diff --git a/tqdm/_tqdm.py b/tqdm/_tqdm.py
index 694318ee7..7fc496277 100644
--- a/tqdm/_tqdm.py
+++ b/tqdm/_tqdm.py
@@ -1,7 +1,9 @@
+from warnings import warn
+
 from .std import *  # NOQA
 from .std import __all__  # NOQA
 from .std import TqdmDeprecationWarning
-from warnings import warn
+
 warn("This function will be removed in tqdm==5.0.0\n"
      "Please use `tqdm.std.*` instead of `tqdm._tqdm.*`",
      TqdmDeprecationWarning, stacklevel=2)
diff --git a/tqdm/_tqdm_gui.py b/tqdm/_tqdm_gui.py
index 541f104fb..f32aa894f 100644
--- a/tqdm/_tqdm_gui.py
+++ b/tqdm/_tqdm_gui.py
@@ -1,7 +1,9 @@
+from warnings import warn
+
 from .gui import *  # NOQA
 from .gui import __all__  # NOQA
 from .std import TqdmDeprecationWarning
-from warnings import warn
+
 warn("This function will be removed in tqdm==5.0.0\n"
      "Please use `tqdm.gui.*` instead of `tqdm._tqdm_gui.*`",
      TqdmDeprecationWarning, stacklevel=2)
diff --git a/tqdm/_tqdm_notebook.py b/tqdm/_tqdm_notebook.py
index dde999817..f225fbf5b 100644
--- a/tqdm/_tqdm_notebook.py
+++ b/tqdm/_tqdm_notebook.py
@@ -1,7 +1,9 @@
+from warnings import warn
+
 from .notebook import *  # NOQA
 from .notebook import __all__  # NOQA
 from .std import TqdmDeprecationWarning
-from warnings import warn
+
 warn("This function will be removed in tqdm==5.0.0\n"
      "Please use `tqdm.notebook.*` instead of `tqdm._tqdm_notebook.*`",
      TqdmDeprecationWarning, stacklevel=2)
diff --git a/tqdm/_utils.py b/tqdm/_utils.py
index 915b5106b..5bf54bb8c 100644
--- a/tqdm/_utils.py
+++ b/tqdm/_utils.py
@@ -1,6 +1,12 @@
-from .utils import CUR_OS, IS_WIN, IS_NIX, RE_ANSI, _range, _unich, _unicode, colorama, _basestring, FormatReplace, Comparable, SimpleTextIOWrapper, _is_utf, _supports_unicode, _is_ascii, _screen_shape_wrapper, _screen_shape_windows, _screen_shape_tput, _screen_shape_linux, _environ_cols_wrapper, _term_move_up  # NOQA
-from .std import TqdmDeprecationWarning
 from warnings import warn
+
+from .std import TqdmDeprecationWarning
+from .utils import (  # NOQA
+    CUR_OS, IS_NIX, IS_WIN, RE_ANSI, Comparable, FormatReplace, SimpleTextIOWrapper,
+    _basestring, _environ_cols_wrapper, _is_ascii, _is_utf, _range, _screen_shape_linux,
+    _screen_shape_tput, _screen_shape_windows, _screen_shape_wrapper, _supports_unicode,
+    _term_move_up, _unich, _unicode, colorama)
+
 warn("This function will be removed in tqdm==5.0.0\n"
      "Please use `tqdm.utils.*` instead of `tqdm._utils.*`",
      TqdmDeprecationWarning, stacklevel=2)
diff --git a/tqdm/asyncio.py b/tqdm/asyncio.py
index 2ac377e30..e8ac90325 100644
--- a/tqdm/asyncio.py
+++ b/tqdm/asyncio.py
@@ -7,8 +7,10 @@
 >>> async for i in trange(10):
 ...     ...
 """
-from .std import tqdm as std_tqdm
 import asyncio
+
+from .std import tqdm as std_tqdm
+
 __author__ = {"github.com/": ["casperdcl"]}
 __all__ = ['tqdm_asyncio', 'tarange', 'tqdm', 'trange']
 
@@ -44,7 +46,7 @@ async def __anext__(self):
         except StopIteration:
             self.close()
             raise StopAsyncIteration
-        except:
+        except BaseException:
             self.close()
             raise
 
diff --git a/tqdm/auto.py b/tqdm/auto.py
index 9dbf50d44..370c180d6 100644
--- a/tqdm/auto.py
+++ b/tqdm/auto.py
@@ -14,7 +14,9 @@
 """
 import sys
 import warnings
+
 from .std import TqdmExperimentalWarning
+
 with warnings.catch_warnings():
     warnings.simplefilter("ignore", category=TqdmExperimentalWarning)
     from .autonotebook import tqdm as notebook_tqdm
diff --git a/tqdm/autonotebook.py b/tqdm/autonotebook.py
index 869475d91..b032061bf 100644
--- a/tqdm/autonotebook.py
+++ b/tqdm/autonotebook.py
@@ -15,12 +15,13 @@
         raise ImportError("console")
     if 'VSCODE_PID' in os.environ:  # pragma: no cover
         raise ImportError("vscode")
-except:
+except Exception:
     from .std import tqdm, trange
 else:  # pragma: no cover
+    from warnings import warn
+
     from .notebook import tqdm, trange
     from .std import TqdmExperimentalWarning
-    from warnings import warn
     warn("Using `tqdm.autonotebook.tqdm` in notebook mode."
          " Use `tqdm.tqdm` instead to force console mode"
          " (e.g. in jupyter console)", TqdmExperimentalWarning, stacklevel=2)
diff --git a/tqdm/cli.py b/tqdm/cli.py
index 703c2cea2..7b7338458 100644
--- a/tqdm/cli.py
+++ b/tqdm/cli.py
@@ -1,13 +1,14 @@
 """
 Module version for monitoring CLI pipes (`... | python -m tqdm | ...`).
 """
-from ast import literal_eval as numeric
 import logging
 import re
 import sys
+from ast import literal_eval as numeric
 
-from .std import tqdm, TqdmTypeError, TqdmKeyError
+from .std import TqdmKeyError, TqdmTypeError, tqdm
 from .version import __version__
+
 __all__ = ["main"]
 log = logging.getLogger(__name__)
 
@@ -32,7 +33,7 @@ def cast(val, typ):
             raise TqdmTypeError(val + ' : ' + typ)
     try:
         return eval(typ + '("' + val + '")')
-    except:
+    except Exception:
         if typ == 'chr':
             return chr(ord(eval('"' + val + '"'))).encode()
         else:
@@ -221,7 +222,7 @@ def main(fp=sys.stderr, argv=None):
         update_to = tqdm_args.pop('update_to', False)
         if sum((delim_per_char, update, update_to)) > 1:
             raise TqdmKeyError("Can only have one of --bytes --update --update_to")
-    except:
+    except Exception:
         fp.write('\nError:\nUsage:\n  tqdm [--help | options]\n')
         for i in sys.stdin:
             sys.stdout.write(i)
@@ -244,7 +245,8 @@ def write(_):
         if manpath or comppath:
             from os import path
             from shutil import copyfile
-            from pkg_resources import resource_filename, Requirement
+
+            from pkg_resources import Requirement, resource_filename
 
             def cp(src, dst):
                 """copies from src path to dst"""
diff --git a/tqdm/contrib/__init__.py b/tqdm/contrib/__init__.py
index dab707b40..f9dfa1713 100644
--- a/tqdm/contrib/__init__.py
+++ b/tqdm/contrib/__init__.py
@@ -3,11 +3,13 @@
 
 Subpackages contain potentially unstable extensions.
 """
+import sys
+from functools import wraps
+
 from tqdm import tqdm
 from tqdm.auto import tqdm as tqdm_auto
 from tqdm.utils import ObjectWrapper
-from functools import wraps
-import sys
+
 __author__ = {"github.com/": ["casperdcl"]}
 __all__ = ['tenumerate', 'tzip', 'tmap']
 
diff --git a/tqdm/contrib/bells.py b/tqdm/contrib/bells.py
index a73ef6904..01e56b90e 100644
--- a/tqdm/contrib/bells.py
+++ b/tqdm/contrib/bells.py
@@ -9,8 +9,8 @@
     + uses `${TQDM_DISCORD_TOKEN}` and `${TQDM_DISCORD_CHANNEL_ID}`
 """
 __all__ = ['tqdm', 'trange']
-from os import getenv
 import warnings
+from os import getenv
 
 if getenv("TQDM_TELEGRAM_TOKEN") and getenv("TQDM_TELEGRAM_CHAT_ID"):
     from tqdm.contrib.telegram import tqdm, trange
diff --git a/tqdm/contrib/concurrent.py b/tqdm/contrib/concurrent.py
index 7d2047b79..5ca683bba 100644
--- a/tqdm/contrib/concurrent.py
+++ b/tqdm/contrib/concurrent.py
@@ -2,9 +2,12 @@
 Thin wrappers around `concurrent.futures`.
 """
 from __future__ import absolute_import
+
+from contextlib import contextmanager
+
 from tqdm import TqdmWarning
 from tqdm.auto import tqdm as tqdm_auto
-from contextlib import contextmanager
+
 try:
     from operator import length_hint
 except ImportError:
@@ -23,6 +26,7 @@ def length_hint(it, default=0):
         def cpu_count():
             return 4
 import sys
+
 __author__ = {"github.com/": ["casperdcl"]}
 __all__ = ['thread_map', 'process_map']
 
@@ -60,7 +64,7 @@ def _executor_map(PoolExecutor, fn, *iterables, **tqdm_kwargs):
     chunksize = kwargs.pop("chunksize", 1)
     lock_name = kwargs.pop("lock_name", "")
     with ensure_lock(tqdm_class, lock_name=lock_name) as lk:
-        pool_kwargs = dict(max_workers=max_workers)
+        pool_kwargs = {'max_workers': max_workers}
         sys_version = sys.version_info[:2]
         if sys_version >= (3, 7):
             # share lock in case workers are already using `tqdm`
diff --git a/tqdm/contrib/discord.py b/tqdm/contrib/discord.py
index f28c8e73f..011fece71 100644
--- a/tqdm/contrib/discord.py
+++ b/tqdm/contrib/discord.py
@@ -10,6 +10,7 @@
 https://raw.githubusercontent.com/tqdm/img/src/screenshot-discord.png)
 """
 from __future__ import absolute_import
+
 import logging
 from os import getenv
 
@@ -20,7 +21,9 @@
 
 from tqdm.auto import tqdm as tqdm_auto
 from tqdm.utils import _range
+
 from .utils_worker import MonoWorker
+
 __author__ = {"github.com/": ["casperdcl"]}
 __all__ = ['DiscordIO', 'tqdm_discord', 'tdrange', 'tqdm', 'trange']
 
diff --git a/tqdm/contrib/itertools.py b/tqdm/contrib/itertools.py
index 96bbb2abc..c02083d07 100644
--- a/tqdm/contrib/itertools.py
+++ b/tqdm/contrib/itertools.py
@@ -2,8 +2,11 @@
 Thin wrappers around `itertools`.
 """
 from __future__ import absolute_import
-from tqdm.auto import tqdm as tqdm_auto
+
 import itertools
+
+from tqdm.auto import tqdm as tqdm_auto
+
 __author__ = {"github.com/": ["casperdcl"]}
 __all__ = ['product']
 
diff --git a/tqdm/contrib/telegram.py b/tqdm/contrib/telegram.py
index 07643e645..d91889a2c 100644
--- a/tqdm/contrib/telegram.py
+++ b/tqdm/contrib/telegram.py
@@ -10,13 +10,16 @@
 https://raw.githubusercontent.com/tqdm/img/src/screenshot-telegram.gif)
 """
 from __future__ import absolute_import
+
 from os import getenv
 
 from requests import Session
 
 from tqdm.auto import tqdm as tqdm_auto
 from tqdm.utils import _range
+
 from .utils_worker import MonoWorker
+
 __author__ = {"github.com/": ["casperdcl"]}
 __all__ = ['TelegramIO', 'tqdm_telegram', 'ttgrange', 'tqdm', 'trange']
 
@@ -35,8 +38,8 @@ def __init__(self, token, chat_id):
         try:
             res = session.post(
                 self.API + '%s/sendMessage' % self.token,
-                data=dict(text='`' + self.text + '`', chat_id=self.chat_id,
-                          parse_mode='MarkdownV2'))
+                data={'text': '`' + self.text + '`', 'chat_id': self.chat_id,
+                      'parse_mode': 'MarkdownV2'})
         except Exception as e:
             tqdm_auto.write(str(e))
         else:
@@ -53,8 +56,8 @@ def write(self, s):
         try:
             future = self.submit(
                 self.session.post, self.API + '%s/editMessageText' % self.token,
-                data=dict(text='`' + s + '`', chat_id=self.chat_id,
-                          message_id=self.message_id, parse_mode='MarkdownV2'))
+                data={'text': '`' + s + '`', 'chat_id': self.chat_id,
+                      'message_id': self.message_id, 'parse_mode': 'MarkdownV2'})
         except Exception as e:
             tqdm_auto.write(str(e))
         else:
diff --git a/tqdm/contrib/utils_worker.py b/tqdm/contrib/utils_worker.py
index 26c66cd16..d3fcf0b67 100644
--- a/tqdm/contrib/utils_worker.py
+++ b/tqdm/contrib/utils_worker.py
@@ -3,10 +3,11 @@
 """
 from __future__ import absolute_import
 
-from concurrent.futures import ThreadPoolExecutor
 from collections import deque
+from concurrent.futures import ThreadPoolExecutor
 
 from tqdm.auto import tqdm as tqdm_auto
+
 __author__ = {"github.com/": ["casperdcl"]}
 __all__ = ['MonoWorker']
 
diff --git a/tqdm/gui.py b/tqdm/gui.py
index d596be9e1..2c90f413e 100644
--- a/tqdm/gui.py
+++ b/tqdm/gui.py
@@ -9,13 +9,15 @@
 """
 # future division is important to divide integers and get as
 # a result precise floating numbers (instead of truncated int)
-from __future__ import division, absolute_import
-# import compatibility functions and utilities
-from .utils import _range
+from __future__ import absolute_import, division
+
+from warnings import warn
+
 # to inherit from the tqdm class
-from .std import tqdm as std_tqdm
 from .std import TqdmExperimentalWarning
-from warnings import warn
+from .std import tqdm as std_tqdm
+# import compatibility functions and utilities
+from .utils import _range
 
 __author__ = {"github.com/": ["casperdcl", "lrq3000"]}
 __all__ = ['tqdm_gui', 'tgrange', 'tqdm', 'trange']
@@ -29,9 +31,10 @@ class tqdm_gui(std_tqdm):  # pragma: no cover
     # TODO: @classmethod: write() on GUI?
 
     def __init__(self, *args, **kwargs):
+        from collections import deque
+
         import matplotlib as mpl
         import matplotlib.pyplot as plt
-        from collections import deque
         kwargs = kwargs.copy()
         kwargs['gui'] = True
         colour = kwargs.pop('colour', 'g')
diff --git a/tqdm/keras.py b/tqdm/keras.py
index 342af9f74..8587bb09e 100644
--- a/tqdm/keras.py
+++ b/tqdm/keras.py
@@ -1,7 +1,10 @@
 from __future__ import absolute_import, division
-from .auto import tqdm as tqdm_auto
+
 from copy import copy
 from functools import partial
+
+from .auto import tqdm as tqdm_auto
+
 try:
     import keras
 except ImportError as e:
diff --git a/tqdm/notebook.py b/tqdm/notebook.py
index bcda1050b..54d694c69 100644
--- a/tqdm/notebook.py
+++ b/tqdm/notebook.py
@@ -9,12 +9,14 @@
 """
 # future division is important to divide integers and get as
 # a result precise floating numbers (instead of truncated int)
-from __future__ import division, absolute_import
+from __future__ import absolute_import, division
+
 # import compatibility functions and utilities
 import sys
-from .utils import _range
+
 # to inherit from the tqdm class
 from .std import tqdm as std_tqdm
+from .utils import _range
 
 if True:  # pragma: no cover
     # import IPython/Jupyter base widget and display utilities
@@ -40,17 +42,19 @@
 
     try:  # IPython 4.x / 3.x
         if IPY == 32:
+            from IPython.html.widgets import HTML
             from IPython.html.widgets import FloatProgress as IProgress
-            from IPython.html.widgets import HBox, HTML
+            from IPython.html.widgets import HBox
             IPY = 3
         else:
+            from ipywidgets import HTML
             from ipywidgets import FloatProgress as IProgress
-            from ipywidgets import HBox, HTML
+            from ipywidgets import HBox
     except ImportError:
         try:  # IPython 2.x
-            from IPython.html.widgets import FloatProgressWidget as IProgress
-            from IPython.html.widgets import ContainerWidget as HBox
             from IPython.html.widgets import HTML
+            from IPython.html.widgets import ContainerWidget as HBox
+            from IPython.html.widgets import FloatProgressWidget as IProgress
             IPY = 2
         except ImportError:
             IPY = 0
diff --git a/tqdm/std.py b/tqdm/std.py
index b6567dc59..308887cf0 100644
--- a/tqdm/std.py
+++ b/tqdm/std.py
@@ -8,6 +8,8 @@
 ...     ...
 """
 from __future__ import absolute_import, division
+
+import sys
 from collections import OrderedDict
 from contextlib import contextmanager
 from datetime import datetime, timedelta
@@ -15,13 +17,12 @@
 from time import time
 from warnings import warn
 from weakref import WeakSet
-import sys
 
-from .utils import _supports_unicode, _screen_shape_wrapper, _range, _unich, \
-    _term_move_up, _unicode, _basestring, \
-    Comparable, _is_ascii, FormatReplace, disp_len, disp_trim, \
-    SimpleTextIOWrapper, DisableOnWriteError, CallbackIOWrapper
 from ._monitor import TMonitor
+from .utils import (
+    CallbackIOWrapper, Comparable, DisableOnWriteError, FormatReplace,
+    SimpleTextIOWrapper, _basestring, _is_ascii, _range, _screen_shape_wrapper,
+    _supports_unicode, _term_move_up, _unich, _unicode, disp_len, disp_trim)
 
 __author__ = "https://github.com/tqdm/tqdm#contributions"
 __all__ = ['tqdm', 'trange',
@@ -147,9 +148,9 @@ class Bar(object):
     BLANK = "  "
     COLOUR_RESET = '\x1b[0m'
     COLOUR_RGB = '\x1b[38;2;%d;%d;%dm'
-    COLOURS = dict(BLACK='\x1b[30m', RED='\x1b[31m', GREEN='\x1b[32m',
-                   YELLOW='\x1b[33m', BLUE='\x1b[34m', MAGENTA='\x1b[35m',
-                   CYAN='\x1b[36m', WHITE='\x1b[37m')
+    COLOURS = {'BLACK': '\x1b[30m', 'RED': '\x1b[31m', 'GREEN': '\x1b[32m',
+               'YELLOW': '\x1b[33m', 'BLUE': '\x1b[34m', 'MAGENTA': '\x1b[35m',
+               'CYAN': '\x1b[36m', 'WHITE': '\x1b[37m'}
 
     def __init__(self, frac, default_len=10, charset=UTF, colour=None):
         if not 0 <= frac <= 1:
@@ -188,7 +189,7 @@ def __format__(self, format_spec):
         if format_spec:
             _type = format_spec[-1].lower()
             try:
-                charset = dict(a=self.ASCII, u=self.UTF, b=self.BLANK)[_type]
+                charset = {'a': self.ASCII, 'u': self.UTF, 'b': self.BLANK}[_type]
             except KeyError:
                 charset = self.charset
             else:
@@ -575,8 +576,8 @@ def __new__(cls, *_, **__):
     @classmethod
     def _get_free_pos(cls, instance=None):
         """Skips specified instance."""
-        positions = set(abs(inst.pos) for inst in cls._instances
-                        if inst is not instance and hasattr(inst, "pos"))
+        positions = {abs(inst.pos) for inst in cls._instances
+                     if inst is not instance and hasattr(inst, "pos")}
         return min(set(range(len(positions) + 1)).difference(positions))
 
     @classmethod
@@ -709,21 +710,19 @@ def pandas(cls, **tqdm_kwargs):
                 from pandas.core.window import _Rolling_and_Expanding
             except ImportError:  # pandas>=1.2.0
                 try:  # pandas>=1.2.0
-                    from pandas.core.window.rolling import Rolling
                     from pandas.core.window.expanding import Expanding
+                    from pandas.core.window.rolling import Rolling
                     _Rolling_and_Expanding = Rolling, Expanding
-                except ImportError:
+                except ImportError:  # pragma: no cover
                     _Rolling_and_Expanding = None
         try:  # pandas>=0.25.0
-            from pandas.core.groupby.generic import DataFrameGroupBy, \
-                SeriesGroupBy  # , NDFrameGroupBy
+            from pandas.core.groupby.generic import SeriesGroupBy  # , NDFrameGroupBy
+            from pandas.core.groupby.generic import DataFrameGroupBy
         except ImportError:
             try:  # pandas>=0.23.0
-                from pandas.core.groupby.groupby import DataFrameGroupBy, \
-                    SeriesGroupBy
+                from pandas.core.groupby.groupby import DataFrameGroupBy, SeriesGroupBy
             except ImportError:
-                from pandas.core.groupby import DataFrameGroupBy, \
-                    SeriesGroupBy
+                from pandas.core.groupby import DataFrameGroupBy, SeriesGroupBy
         try:  # pandas>=0.23.0
             from pandas.core.groupby.groupby import GroupBy
         except ImportError:
@@ -1424,14 +1423,15 @@ def format_dict(self):
         if self.dynamic_ncols:
             self.ncols, self.nrows = self.dynamic_ncols(self.fp)
         ncols, nrows = self.ncols, self.nrows
-        return dict(
-            n=self.n, total=self.total,
-            elapsed=self._time() - self.start_t if hasattr(self, 'start_t') else 0,
-            ncols=ncols, nrows=nrows, prefix=self.desc, ascii=self.ascii,
-            unit=self.unit, unit_scale=self.unit_scale,
-            rate=self._ema_dn() / self._ema_dt() if self._ema_dt() else None,
-            bar_format=self.bar_format, postfix=self.postfix,
-            unit_divisor=self.unit_divisor, initial=self.initial, colour=self.colour)
+        return {
+            'n': self.n, 'total': self.total,
+            'elapsed': self._time() - self.start_t if hasattr(self, 'start_t') else 0,
+            'ncols': ncols, 'nrows': nrows, 'prefix': self.desc, 'ascii': self.ascii,
+            'unit': self.unit, 'unit_scale': self.unit_scale,
+            'rate': self._ema_dn() / self._ema_dt() if self._ema_dt() else None,
+            'bar_format': self.bar_format, 'postfix': self.postfix,
+            'unit_divisor': self.unit_divisor, 'initial': self.initial,
+            'colour': self.colour}
 
     def display(self, msg=None, pos=None):
         """
diff --git a/tqdm/utils.py b/tqdm/utils.py
index f1d39bba4..96c710f86 100644
--- a/tqdm/utils.py
+++ b/tqdm/utils.py
@@ -1,12 +1,12 @@
 """
 General helpers required for `tqdm.std`.
 """
-from functools import wraps
-from warnings import warn
 import os
 import re
 import subprocess
 import sys
+from functools import wraps
+from warnings import warn
 
 # py2/3 compat
 try:
@@ -53,7 +53,7 @@ class FormatReplace(object):
     >>> a = FormatReplace('something')
     >>> "{:5d}".format(a)
     'something'
-    """
+    """  # NOQA: P102
     def __init__(self, replace=''):
         self.replace = replace
         self.format_called = 0
@@ -141,7 +141,7 @@ def disable_on_exception(tqdm_instance, func):
         def inner(*args, **kwargs):
             try:
                 return func(*args, **kwargs)
-            except (IOError, OSError) as e:
+            except OSError as e:
                 if e.errno != 5:
                     raise
                 tqdm_instance.miniters = float('inf')
@@ -198,7 +198,7 @@ def _is_utf(encoding):
     except Exception:
         try:
             return encoding.lower().startswith('utf-') or ('U8' == encoding)
-        except:
+        except Exception:
             return False
     else:
         return True
@@ -237,8 +237,8 @@ def _screen_shape_wrapper():  # pragma: no cover
 
 def _screen_shape_windows(fp):  # pragma: no cover
     try:
-        from ctypes import windll, create_string_buffer
         import struct
+        from ctypes import create_string_buffer, windll
         from sys import stdin, stdout
 
         io_handle = -12  # assume stderr
@@ -254,7 +254,7 @@ def _screen_shape_windows(fp):  # pragma: no cover
             (_bufx, _bufy, _curx, _cury, _wattr, left, top, right, bottom,
              _maxx, _maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
             return right - left, bottom - top  # +1
-    except:
+    except Exception:
         pass
     return None, None
 
@@ -265,7 +265,7 @@ def _screen_shape_tput(*_):  # pragma: no cover
         import shlex
         return [int(subprocess.check_call(shlex.split('tput ' + i))) - 1
                 for i in ('cols', 'lines')]
-    except:
+    except Exception:
         pass
     return None, None
 
@@ -273,16 +273,16 @@ def _screen_shape_tput(*_):  # pragma: no cover
 def _screen_shape_linux(fp):  # pragma: no cover
 
     try:
-        from termios import TIOCGWINSZ
-        from fcntl import ioctl
         from array import array
+        from fcntl import ioctl
+        from termios import TIOCGWINSZ
     except ImportError:
         return None
     else:
         try:
             rows, cols = array('h', ioctl(fp, TIOCGWINSZ, '\0' * 8))[:2]
             return cols, rows
-        except:
+        except Exception:
             try:
                 return [int(os.environ[i]) - 1 for i in ("COLUMNS", "LINES")]
             except KeyError:


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8ef1b6181..1f13be4e9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,16 +7,38 @@ on:
 jobs:
   check:
     if: github.event_name != 'pull_request' || github.head_ref != 'devel'
-    name: install
     runs-on: ubuntu-latest
+    name: check
     steps:
     - uses: actions/checkout@v2
       with:
         fetch-depth: 0
     - uses: actions/setup-python@v2
+    - name: set PYSHA
+      run: echo "PYSHA=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
+    - uses: actions/cache@v1
       with:
-        python-version: '3.x'
-    - run: pip install .
+        path: ~/.cache/pre-commit
+        key: pre-commit|${{ env.PYSHA }}|${{ hashFiles('.pre-commit-config.yaml') }}
+    - name: dependencies
+      run: |
+        pip install -U pre-commit docutils pygments py-make
+        sudo apt-get install -yqq pandoc
+    - uses: reviewdog/action-setup@v1
+    - if: github.event_name != 'schedule'
+      name: comment
+      run: |
+        if [[ $EVENT == pull_request ]]; then
+          REPORTER=github-pr-review
+        else
+          REPORTER=github-check
+        fi
+        pre-commit run -a todo | reviewdog -efm="%f:%l: %m" -name=TODO -tee -reporter=$REPORTER -filter-mode nofilter
+        pre-commit run -a flake8 | reviewdog -f=pep8 -name=flake8 -tee -reporter=$REPORTER -filter-mode nofilter
+      env:
+        REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        EVENT: ${{ github.event_name }}
+    - run: pre-commit run -a --show-diff-on-failure
   test-os:
     if: github.event_name != 'pull_request' || github.head_ref != 'devel'
     strategy:
@@ -106,26 +128,20 @@ jobs:
       with:
         fetch-depth: 0
     - uses: actions/setup-python@v2
-      with:
-        python-version: '3.x'
     - name: Install
       run: |
         sudo apt-get install -yqq pandoc
         pip install .[dev]
         make build .dockerignore snapcraft.yaml
-    - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
-      uses: casperdcl/deploy-pypi@v1
+    - id: dist
+      uses: casperdcl/deploy-pypi@v2
       with:
         password: ${{ secrets.PYPI_TOKEN }}
         gpg_key: ${{ secrets.GPG_KEY }}
-        skip_existing: true
+        upload: ${{ github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') }}
     - 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)"
-        echo "::set-output name=asset_path_sig::$(ls dist/*.whl.asc 2>/dev/null)"
-        echo "::set-output name=asset_name_sig::$(basename dist/*.whl.asc 2>/dev/null)"
         if [[ $GITHUB_REF == refs/tags/v* ]]; then
           echo ::set-output name=docker_tags::latest,${GITHUB_REF/refs\/tags\/v/}
           echo ::set-output name=snap_channel::stable,candidate,edge
@@ -153,8 +169,8 @@ jobs:
         GITHUB_TOKEN: ${{ secrets.GH_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
     - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
       uses: actions/upload-release-asset@v1
@@ -162,8 +178,8 @@ jobs:
         GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
       with:
         upload_url: ${{ steps.create_release.outputs.upload_url }}
-        asset_path: ${{ steps.collect_assets.outputs.asset_path_sig }}
-        asset_name: ${{ steps.collect_assets.outputs.asset_name_sig }}
+        asset_path: dist/${{ steps.dist.outputs.whl_asc }}
+        asset_name: ${{ steps.dist.outputs.whl_asc }}
         asset_content_type: text/plain
     - uses: snapcore/action-build@v1
       id: snap_build
diff --git a/tests/conftest.py b/tests/conftest.py
index 11b21b222..67170442b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,8 +1,10 @@
 """Shared pytest config."""
 import sys
-from tqdm import tqdm
+
 from pytest import fixture
 
+from tqdm import tqdm
+
 
 @fixture(autouse=True)
 def pretest_posttest():
@@ -25,3 +27,15 @@ def pretest_posttest():
             tqdm._instances.clear()
             raise EnvironmentError(
                 "{0} `tqdm` instances still in existence POST-test".format(n))
+
+
+if sys.version_info[0] > 2:
+    @fixture
+    def capsysbin(capsysbinary):
+        """alias for capsysbinary (py3)"""
+        return capsysbinary
+else:
+    @fixture
+    def capsysbin(capsys):
+        """alias for capsys (py2)"""
+        return capsys
diff --git a/tests/py37_asyncio.py b/tests/py37_asyncio.py
index d76f5a6f5..be1903f21 100644
--- a/tests/py37_asyncio.py
+++ b/tests/py37_asyncio.py
@@ -1,12 +1,11 @@
+import asyncio
 from functools import partial
 from sys import platform
 from time import time
-import asyncio
 
-from pytest import mark
+from tqdm.asyncio import tarange, tqdm_asyncio
 
-from tqdm.asyncio import tqdm_asyncio, tarange
-from .tests_tqdm import StringIO, closing
+from .tests_tqdm import StringIO, closing, mark
 
 tqdm = partial(tqdm_asyncio, miniters=0, mininterval=0)
 trange = partial(tarange, miniters=0, mininterval=0)
@@ -95,6 +94,7 @@ async def test_coroutines():
         assert '10it' in our_file.getvalue()
 
 
+@mark.slow
 @mark.asyncio
 @mark.parametrize("tol", [0.2 if platform.startswith("darwin") else 0.1])
 async def test_as_completed(capsys, tol):
diff --git a/tests/tests_asyncio.py b/tests/tests_asyncio.py
index 30f85d7c4..6f089264c 100644
--- a/tests/tests_asyncio.py
+++ b/tests/tests_asyncio.py
@@ -4,8 +4,8 @@
 if sys.version_info[:2] > (3, 6):
     from .py37_asyncio import *  # NOQA, pylint: disable=wildcard-import
 else:
-    import pytest
+    from .tests_tqdm import skip
     try:
-        pytest.skip("async not supported", allow_module_level=True)
+        skip("async not supported", allow_module_level=True)
     except TypeError:
         pass
diff --git a/tests/tests_concurrent.py b/tests/tests_concurrent.py
index ebef677ef..5cd439c94 100644
--- a/tests/tests_concurrent.py
+++ b/tests/tests_concurrent.py
@@ -1,12 +1,11 @@
 """
 Tests for `tqdm.contrib.concurrent`.
 """
-from warnings import catch_warnings
+from pytest import warns
 
-from pytest import mark
+from tqdm.contrib.concurrent import process_map, thread_map
 
-from tqdm.contrib.concurrent import thread_map, process_map
-from .tests_tqdm import importorskip, skip, StringIO, closing
+from .tests_tqdm import StringIO, TqdmWarning, closing, importorskip, mark, skip
 
 
 def incr(x):
@@ -42,8 +41,9 @@ def test_process_map():
                                             (['x' * 100, ('x',) * 1001], True)])
 def test_chunksize_warning(iterables, should_warn):
     """Test contrib.concurrent.process_map chunksize warnings"""
-    patch = importorskip("unittest.mock").patch
+    patch = importorskip('unittest.mock').patch
     with patch('tqdm.contrib.concurrent._executor_map'):
-        with catch_warnings(record=True) as w:
+        if should_warn:
+            warns(TqdmWarning, process_map, incr, *iterables)
+        else:
             process_map(incr, *iterables)
-            assert should_warn == bool(w)
diff --git a/tests/tests_contrib.py b/tests/tests_contrib.py
index eca3d18d9..cf684eeae 100644
--- a/tests/tests_contrib.py
+++ b/tests/tests_contrib.py
@@ -3,8 +3,9 @@
 """
 import sys
 
-from tqdm.contrib import tenumerate, tzip, tmap
-from .tests_tqdm import importorskip, StringIO, closing
+from tqdm.contrib import tenumerate, tmap, tzip
+
+from .tests_tqdm import StringIO, closing, importorskip
 
 
 def incr(x):
@@ -28,9 +29,9 @@ def test_enumerate():
 
 def test_enumerate_numpy():
     """Test contrib.tenumerate(numpy.ndarray)"""
-    np = importorskip("numpy")
+    np = importorskip('numpy')
     with closing(StringIO()) as our_file:
-        a = np.random.random((42, 1337))
+        a = np.random.random((42, 7))
         assert list(tenumerate(a, file=our_file)) == list(np.ndenumerate(a))
 
 
diff --git a/tests/tests_itertools.py b/tests/tests_itertools.py
index ede9949d5..8aac70da7 100644
--- a/tests/tests_itertools.py
+++ b/tests/tests_itertools.py
@@ -4,6 +4,7 @@
 import itertools
 
 from tqdm.contrib.itertools import product
+
 from .tests_tqdm import StringIO, closing
 
 
diff --git a/tests/tests_keras.py b/tests/tests_keras.py
index 9d1b21e19..6d044207e 100644
--- a/tests/tests_keras.py
+++ b/tests/tests_keras.py
@@ -1,17 +1,21 @@
 from __future__ import division
 
 from tqdm import tqdm
-from .tests_tqdm import importorskip, StringIO, closing
 
+from .tests_tqdm import StringIO, closing, importorskip, mark
 
+pytestmark = mark.slow
+
+
+@mark.filterwarnings("ignore:.*:DeprecationWarning")
 def test_keras():
     """Test tqdm.keras.TqdmCallback"""
-    TqdmCallback = importorskip("tqdm.keras").TqdmCallback
-    np = importorskip("numpy")
+    TqdmCallback = importorskip('tqdm.keras').TqdmCallback
+    np = importorskip('numpy')
     try:
         import keras as K
     except ImportError:
-        K = importorskip("tensorflow.keras")
+        K = importorskip('tensorflow.keras')
 
     # 1D autoencoder
     dtype = np.float32
diff --git a/tests/tests_main.py b/tests/tests_main.py
index 990348d4b..44d0b7878 100644
--- a/tests/tests_main.py
+++ b/tests/tests_main.py
@@ -1,279 +1,244 @@
 """Test CLI usage."""
-from io import open as io_open
-from os import path
-from shutil import rmtree
-from tempfile import mkdtemp
-import sys
+import logging
 import subprocess
+import sys
+from functools import wraps
+from os import linesep
 
-from tqdm.cli import main, TqdmKeyError, TqdmTypeError
+from tqdm.cli import TqdmKeyError, TqdmTypeError, main
 from tqdm.utils import IS_WIN
-from .tests_tqdm import skip, _range, closing, UnicodeIO, StringIO, BytesIO
 
+from .tests_tqdm import BytesIO, _range, closing, mark, raises
 
-def _sh(*cmd, **kwargs):
-    return subprocess.Popen(cmd, stdout=subprocess.PIPE,
-                            **kwargs).communicate()[0].decode('utf-8')
 
+def restore_sys(func):
+    """Decorates `func(capsysbin)` to save & restore `sys.(stdin|argv)`."""
+    @wraps(func)
+    def inner(capsysbin):
+        """function requiring capsysbin which may alter `sys.(stdin|argv)`"""
+        _SYS = sys.stdin, sys.argv
+        try:
+            res = func(capsysbin)
+        finally:
+            sys.stdin, sys.argv = _SYS
+        return res
 
-class Null(object):
-    def __call__(self, *_, **__):
-        return self
-
-    def __getattr__(self, _):
-        return self
+    return inner
 
 
-NULL = Null()
+def norm(bytestr):
+    """Normalise line endings."""
+    return bytestr if linesep == "\n" else bytestr.replace(linesep.encode(), b"\n")
 
 
+@mark.slow
 def test_pipes():
     """Test command line pipes"""
-    ls_out = _sh('ls').replace('\r\n', '\n')
-    ls = subprocess.Popen('ls', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-    res = _sh(sys.executable, '-c', 'from tqdm.cli import main; main()',
-              stdin=ls.stdout, stderr=subprocess.STDOUT)
-    ls.wait()
+    ls_out = subprocess.check_output(['ls'])
+    ls = subprocess.Popen(['ls'], stdout=subprocess.PIPE)
+    res = subprocess.Popen(
+        [sys.executable, '-c', 'from tqdm.cli import main; main()'],
+        stdin=ls.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = res.communicate()
+    assert ls.poll() == 0
 
     # actual test:
+    assert norm(ls_out) == norm(out)
+    assert b"it/s" in err
 
-    assert ls_out in res.replace('\r\n', '\n')
 
+if sys.version_info[:2] >= (3, 8):
+    test_pipes = mark.filterwarnings("ignore:unclosed file:ResourceWarning")(
+        test_pipes)
 
-# WARNING: this should be the last test as it messes with sys.stdin, argv
-def test_main():
-    """Test misc CLI options"""
-    _SYS = sys.stdin, sys.argv
-    _STDOUT = sys.stdout
-    N = 123
 
+def test_main_import():
+    """Test main CLI import"""
+    N = 123
+    _SYS = sys.stdin, sys.argv
     # test direct import
     sys.stdin = [str(i).encode() for i in _range(N)]
     sys.argv = ['', '--desc', 'Test CLI import',
                 '--ascii', 'True', '--unit_scale', 'True']
-    import tqdm.__main__  # NOQA
-    sys.stderr.write("Test misc CLI options ... ")
+    try:
+        import tqdm.__main__  # NOQA
+    finally:
+        sys.stdin, sys.argv = _SYS
+
+
+@restore_sys
+def test_main_bytes(capsysbin):
+    """Test CLI --bytes"""
+    N = 123
 
     # test --delim
     IN_DATA = '\0'.join(map(str, _range(N))).encode()
     with closing(BytesIO()) as sys.stdin:
-        sys.argv = ['', '--desc', 'Test CLI delim',
-                    '--ascii', 'True', '--delim', r'\0', '--buf_size', '64']
         sys.stdin.write(IN_DATA)
         # sys.stdin.write(b'\xff')  # TODO
         sys.stdin.seek(0)
-        with closing(UnicodeIO()) as fp:
-            main(fp=fp)
-            assert str(N) + "it" in fp.getvalue()
+        main(sys.stderr, ['--desc', 'Test CLI delim', '--ascii', 'True',
+                          '--delim', r'\0', '--buf_size', '64'])
+        out, err = capsysbin.readouterr()
+        assert out == IN_DATA
+        assert str(N) + "it" in err.decode("U8")
 
     # test --bytes
     IN_DATA = IN_DATA.replace(b'\0', b'\n')
     with closing(BytesIO()) as sys.stdin:
         sys.stdin.write(IN_DATA)
         sys.stdin.seek(0)
-        sys.argv = ['', '--ascii', '--bytes=True', '--unit_scale', 'False']
-        with closing(UnicodeIO()) as fp:
-            main(fp=fp)
-            assert str(len(IN_DATA)) in fp.getvalue()
+        main(sys.stderr, ['--ascii', '--bytes=True', '--unit_scale', 'False'])
+        out, err = capsysbin.readouterr()
+        assert out == IN_DATA
+        assert str(len(IN_DATA)) + "B" in err.decode("U8")
 
-    # test --log
-    sys.stdin = [str(i).encode() for i in _range(N)]
-    # with closing(UnicodeIO()) as fp:
-    main(argv=['--log', 'DEBUG'], fp=NULL)
-    main(argv=['--log=DEBUG'], fp=NULL)
-    # assert "DEBUG:" in sys.stdout.getvalue()
 
+@mark.skipif(sys.version_info[0] == 2, reason="no caplog on py2")
+def test_main_log(capsysbin, caplog):
+    """Test CLI --log"""
+    _SYS = sys.stdin, sys.argv
+    N = 123
+    sys.stdin = [(str(i) + '\n').encode() for i in _range(N)]
+    IN_DATA = b''.join(sys.stdin)
     try:
-        # test --tee
-        IN_DATA = IN_DATA.decode()
-        with closing(StringIO()) as sys.stdout:
-            with closing(StringIO()) as sys.stdin:
-                sys.stdin.write(IN_DATA)
-
-                sys.stdin.seek(0)
-                with closing(UnicodeIO()) as fp:
-                    main(argv=['--mininterval', '0', '--miniters', '1'], fp=fp)
-                    res = len(fp.getvalue())
-                    # assert len(fp.getvalue()) < len(sys.stdout.getvalue())
-
-                sys.stdin.seek(0)
-                with closing(UnicodeIO()) as fp:
-                    main(argv=['--tee', '--mininterval', '0', '--miniters', '1'], fp=fp)
-                    # spaces to clear intermediate lines could increase length
-                    assert len(fp.getvalue()) >= res + len(IN_DATA)
-
-        # test --null
-        with closing(StringIO()) as sys.stdout:
-            with closing(StringIO()) as sys.stdin:
-                sys.stdin.write(IN_DATA)
-
-                sys.stdin.seek(0)
-                with closing(UnicodeIO()) as fp:
-                    main(argv=['--null'], fp=fp)
-                    assert not sys.stdout.getvalue()
-
-            with closing(StringIO()) as sys.stdin:
-                sys.stdin.write(IN_DATA)
-
-                sys.stdin.seek(0)
-                with closing(UnicodeIO()) as fp:
-                    main(argv=[], fp=fp)
-                    assert sys.stdout.getvalue()
+        with caplog.at_level(logging.INFO):
+            main(sys.stderr, ['--log', 'INFO'])
+            out, err = capsysbin.readouterr()
+            assert norm(out) == IN_DATA and b"123/123" in err
+            assert not caplog.record_tuples
+        with caplog.at_level(logging.DEBUG):
+            main(sys.stderr, ['--log', 'DEBUG'])
+            out, err = capsysbin.readouterr()
+            assert norm(out) == IN_DATA and b"123/123" in err
+            assert caplog.record_tuples
     finally:
-        sys.stdout = _STDOUT
+        sys.stdin, sys.argv = _SYS
 
-    # test integer --update
-    IN_DATA = IN_DATA.encode()
-    with closing(BytesIO()) as sys.stdin:
-        sys.stdin.write(IN_DATA)
 
-        sys.stdin.seek(0)
-        with closing(UnicodeIO()) as fp:
-            main(argv=['--update'], fp=fp)
-            res = fp.getvalue()
-            assert str(N // 2 * N) + "it" in res  # arithmetic sum formula
-
-    # test integer --update --delim
-    with closing(BytesIO()) as sys.stdin:
-        sys.stdin.write(IN_DATA.replace(b'\n', b'D'))
+@restore_sys
+def test_main(capsysbin):
+    """Test misc CLI options"""
+    N = 123
+    sys.stdin = [(str(i) + '\n').encode() for i in _range(N)]
+    IN_DATA = b''.join(sys.stdin)
+
+    # test --tee
+    main(sys.stderr, ['--mininterval', '0', '--miniters', '1'])
+    out, err = capsysbin.readouterr()
+    assert norm(out) == IN_DATA and b"123/123" in err
+    assert N <= len(err.split(b"\r")) < N + 5
+
+    len_err = len(err)
+    main(sys.stderr, ['--tee', '--mininterval', '0', '--miniters', '1'])
+    out, err = capsysbin.readouterr()
+    assert norm(out) == IN_DATA and b"123/123" in err
+    # spaces to clear intermediate lines could increase length
+    assert len_err + len(norm(out)) <= len(err)
+
+    # test --null
+    main(sys.stderr, ['--null'])
+    out, err = capsysbin.readouterr()
+    assert not out and b"123/123" in err
 
-        sys.stdin.seek(0)
-        with closing(UnicodeIO()) as fp:
-            main(argv=['--update', '--delim', 'D'], fp=fp)
-            res = fp.getvalue()
-            assert str(N // 2 * N) + "it" in res  # arithmetic sum formula
+    # test integer --update
+    main(sys.stderr, ['--update'])
+    out, err = capsysbin.readouterr()
+    assert norm(out) == IN_DATA
+    assert (str(N // 2 * N) + "it").encode() in err, "expected arithmetic sum formula"
 
     # test integer --update_to
-    with closing(BytesIO()) as sys.stdin:
-        sys.stdin.write(IN_DATA)
+    main(sys.stderr, ['--update-to'])
+    out, err = capsysbin.readouterr()
+    assert norm(out) == IN_DATA
+    assert (str(N - 1) + "it").encode() in err
+    assert (str(N) + "it").encode() not in err
 
-        sys.stdin.seek(0)
-        with closing(UnicodeIO()) as fp:
-            main(argv=['--update-to'], fp=fp)
-            res = fp.getvalue()
-            assert str(N - 1) + "it" in res
-            assert str(N) + "it" not in res
-
-    # test integer --update_to --delim
     with closing(BytesIO()) as sys.stdin:
         sys.stdin.write(IN_DATA.replace(b'\n', b'D'))
 
+        # test integer --update --delim
         sys.stdin.seek(0)
-        with closing(UnicodeIO()) as fp:
-            main(argv=['--update-to', '--delim', 'D'], fp=fp)
-            res = fp.getvalue()
-            assert str(N - 1) + "it" in res
-            assert str(N) + "it" not in res
-
-    # test float --update_to
-    IN_DATA = '\n'.join((str(i / 2.0) for i in _range(N))).encode()
-    with closing(BytesIO()) as sys.stdin:
-        sys.stdin.write(IN_DATA)
+        main(sys.stderr, ['--update', '--delim', 'D'])
+        out, err = capsysbin.readouterr()
+        assert out == IN_DATA.replace(b'\n', b'D')
+        assert (str(N // 2 * N) + "it").encode() in err, "expected arithmetic sum"
 
+        # test integer --update_to --delim
         sys.stdin.seek(0)
-        with closing(UnicodeIO()) as fp:
-            main(argv=['--update-to'], fp=fp)
-            res = fp.getvalue()
-            assert str((N - 1) / 2.0) + "it" in res
-            assert str(N / 2.0) + "it" not in res
+        main(sys.stderr, ['--update-to', '--delim', 'D'])
+        out, err = capsysbin.readouterr()
+        assert out == IN_DATA.replace(b'\n', b'D')
+        assert (str(N - 1) + "it").encode() in err
+        assert (str(N) + "it").encode() not in err
 
-    # clean up
-    sys.stdin, sys.argv = _SYS
-
-
-def test_manpath():
+    # test float --update_to
+    sys.stdin = [(str(i / 2.0) + '\n').encode() for i in _range(N)]
+    IN_DATA = b''.join(sys.stdin)
+    main(sys.stderr, ['--update-to'])
+    out, err = capsysbin.readouterr()
+    assert norm(out) == IN_DATA
+    assert (str((N - 1) / 2.0) + "it").encode() in err
+    assert (str(N / 2.0) + "it").encode() not in err
+
+
+@mark.slow
+@mark.skipif(IS_WIN, reason="no manpages on windows")
+def test_manpath(tmp_path):
     """Test CLI --manpath"""
-    if IS_WIN:
-        skip("no manpages on windows")
-    tmp = mkdtemp()
-    man = path.join(tmp, "tqdm.1")
-    assert not path.exists(man)
-    try:
-        main(argv=['--manpath', tmp], fp=NULL)
-    except SystemExit:
-        pass
-    else:
-        raise SystemExit("Expected system exit")
-    assert path.exists(man)
-    rmtree(tmp, True)
+    man = tmp_path / "tqdm.1"
+    assert not man.exists()
+    with raises(SystemExit):
+        main(argv=['--manpath', str(tmp_path)])
+    assert man.is_file()
 
 
-def test_comppath():
+@mark.slow
+@mark.skipif(IS_WIN, reason="no completion on windows")
+def test_comppath(tmp_path):
     """Test CLI --comppath"""
-    if IS_WIN:
-        skip("no manpages on windows")
-    tmp = mkdtemp()
-    man = path.join(tmp, "tqdm_completion.sh")
-    assert not path.exists(man)
-    try:
-        main(argv=['--comppath', tmp], fp=NULL)
-    except SystemExit:
-        pass
-    else:
-        raise SystemExit("Expected system exit")
-    assert path.exists(man)
+    man = tmp_path / "tqdm_completion.sh"
+    assert not man.exists()
+    with raises(SystemExit):
+        main(argv=['--comppath', str(tmp_path)])
+    assert man.is_file()
 
     # check most important options appear
-    with io_open(man, mode='r', encoding='utf-8') as fd:
-        script = fd.read()
-    opts = set([
-        '--help', '--desc', '--total', '--leave', '--ncols', '--ascii',
-        '--dynamic_ncols', '--position', '--bytes', '--nrows', '--delim', '--manpath',
-        '--comppath'])
+    script = man.read_text()
+    opts = {'--help', '--desc', '--total', '--leave', '--ncols', '--ascii',
+            '--dynamic_ncols', '--position', '--bytes', '--nrows', '--delim',
+            '--manpath', '--comppath'}
     assert all(args in script for args in opts)
-    rmtree(tmp, True)
 
 
-def test_exceptions():
+@restore_sys
+def test_exceptions(capsysbin):
     """Test CLI Exceptions"""
-    _SYS = sys.stdin, sys.argv
-    sys.stdin = map(str, _range(123))
+    N = 123
+    sys.stdin = [str(i) + '\n' for i in _range(N)]
+    IN_DATA = ''.join(sys.stdin).encode()
 
-    sys.argv = ['', '-ascii', '-unit_scale', '--bad_arg_u_ment', 'foo']
-    try:
-        main(fp=NULL)
-    except TqdmKeyError as e:
-        if 'bad_arg_u_ment' not in str(e):
-            raise
-    else:
-        raise TqdmKeyError('bad_arg_u_ment')
-
-    sys.argv = ['', '-ascii', '-unit_scale', 'invalid_bool_value']
-    try:
-        main(fp=NULL)
-    except TqdmTypeError as e:
-        if 'invalid_bool_value' not in str(e):
-            raise
-    else:
-        raise TqdmTypeError('invalid_bool_value')
-
-    sys.argv = ['', '-ascii', '--total', 'invalid_int_value']
-    try:
-        main(fp=NULL)
-    except TqdmTypeError as e:
-        if 'invalid_int_value' not in str(e):
-            raise
-    else:
-        raise TqdmTypeError('invalid_int_value')
-
-    sys.argv = ['', '--update', '--update_to']
-    try:
-        main(fp=NULL)
-    except TqdmKeyError as e:
-        if 'Can only have one of --' not in str(e):
-            raise
-    else:
-        raise TqdmKeyError('Cannot have both --update --update_to')
+    with raises(TqdmKeyError, match="bad_arg_u_ment"):
+        main(sys.stderr, argv=['-ascii', '-unit_scale', '--bad_arg_u_ment', 'foo'])
+    out, _ = capsysbin.readouterr()
+    assert norm(out) == IN_DATA
+
+    with raises(TqdmTypeError, match="invalid_bool_value"):
+        main(sys.stderr, argv=['-ascii', '-unit_scale', 'invalid_bool_value'])
+    out, _ = capsysbin.readouterr()
+    assert norm(out) == IN_DATA
+
+    with raises(TqdmTypeError, match="invalid_int_value"):
+        main(sys.stderr, argv=['-ascii', '--total', 'invalid_int_value'])
+    out, _ = capsysbin.readouterr()
+    assert norm(out) == IN_DATA
+
+    with raises(TqdmKeyError, match="Can only have one of --"):
+        main(sys.stderr, argv=['--update', '--update_to'])
+    out, _ = capsysbin.readouterr()
+    assert norm(out) == IN_DATA
 
     # test SystemExits
     for i in ('-h', '--help', '-v', '--version'):
-        sys.argv = ['', i]
-        try:
-            main(fp=NULL)
-        except SystemExit:
-            pass
-        else:
-            raise ValueError('expected SystemExit')
-
-    # clean up
-    sys.stdin, sys.argv = _SYS
+        with raises(SystemExit):
+            main(argv=[i])
diff --git a/tests/tests_pandas.py b/tests/tests_pandas.py
index fa65f82f6..334a97cbb 100644
--- a/tests/tests_pandas.py
+++ b/tests/tests_pandas.py
@@ -1,10 +1,13 @@
 from tqdm import tqdm
-from .tests_tqdm import importorskip, skip, StringIO, closing
 
-random = importorskip("numpy.random")
+from .tests_tqdm import StringIO, closing, importorskip, mark, skip
+
+pytestmark = mark.slow
+
+random = importorskip('numpy.random')
 rand = random.rand
 randint = random.randint
-pd = importorskip("pandas")
+pd = importorskip('pandas')
 
 
 def test_pandas_setup():
@@ -115,7 +118,7 @@ def test_pandas_groupby_apply():
         dfs = pd.DataFrame(randint(0, 50, (500, 3)), columns=list('abc'))
         dfs.groupby(['a']).progress_apply(lambda x: None)
 
-        df2 = df = pd.DataFrame(dict(a=randint(1, 8, 10000), b=rand(10000)))
+        df2 = df = pd.DataFrame({'a': randint(1, 8, 10000), 'b': rand(10000)})
         res1 = df2.groupby("a").apply(max)
         res2 = df2.groupby("a").progress_apply(max)
         assert res1.equals(res2)
@@ -187,9 +190,9 @@ def test_pandas_apply_args_deprecation():
         df.progress_apply(lambda x: None, 1)  # 1 shall cause a warning
         # Check deprecation message
         res = our_file.getvalue()
-        assert all([i in res for i in (
+        assert all(i in res for i in (
             "TqdmDeprecationWarning", "not supported",
-            "keyword arguments instead")])
+            "keyword arguments instead"))
 
 
 def test_pandas_deprecation():
diff --git a/tests/tests_perf.py b/tests/tests_perf.py
index 6a244fbe9..ec9bd1d82 100644
--- a/tests/tests_perf.py
+++ b/tests/tests_perf.py
@@ -1,17 +1,22 @@
-from __future__ import print_function, division
+from __future__ import division, print_function
+
+import sys
 from contextlib import contextmanager
 from functools import wraps
 from time import sleep, time
+
 # Use relative/cpu timer to have reliable timings when there is a sudden load
 try:
     from time import process_time
 except ImportError:
     from time import clock
     process_time = clock
-import sys
 
 from tqdm import tqdm, trange
-from .tests_tqdm import importorskip, skip, _range, patch_lock
+
+from .tests_tqdm import _range, importorskip, mark, patch_lock, skip
+
+pytestmark = mark.slow
 
 
 def cpu_sleep(t):
diff --git a/tests/tests_synchronisation.py b/tests/tests_synchronisation.py
index d5f7bab08..7ee55fb1d 100644
--- a/tests/tests_synchronisation.py
+++ b/tests/tests_synchronisation.py
@@ -1,12 +1,14 @@
 from __future__ import division
+
+import sys
 from functools import wraps
 from threading import Event
 from time import sleep, time
-import sys
 
-from tqdm import tqdm, trange, TMonitor
+from tqdm import TMonitor, tqdm, trange
+
 from .tests_perf import retry_on_except
-from .tests_tqdm import importorskip, skip, StringIO, closing, patch_lock
+from .tests_tqdm import StringIO, closing, importorskip, patch_lock, skip
 
 
 class Time(object):
@@ -200,6 +202,7 @@ def test_imap():
 
     pool = Pool()
     res = list(tqdm(pool.imap(incr, range(100)), disable=True))
+    pool.close()
     assert res[-1] == 100
 
 
@@ -208,7 +211,7 @@ def test_imap():
 @patch_lock(thread=True)
 def test_threadpool():
     """Test concurrent.futures.ThreadPoolExecutor"""
-    ThreadPoolExecutor = importorskip("concurrent.futures").ThreadPoolExecutor
+    ThreadPoolExecutor = importorskip('concurrent.futures').ThreadPoolExecutor
 
     with ThreadPoolExecutor(8) as pool:
         try:
diff --git a/tests/tests_tqdm.py b/tests/tests_tqdm.py
index 02eab10d6..65b2c6ad5 100644
--- a/tests/tests_tqdm.py
+++ b/tests/tests_tqdm.py
@@ -2,29 +2,27 @@
 # Advice: use repr(our_file.read()) to print the full output of tqdm
 # (else '\r' will replace the previous lines and you'll see only the latest.
 
-import sys
 import csv
-import re
 import os
-from functools import wraps
+import re
+import sys
 from contextlib import contextmanager
-from pytest import raises as assert_raises
-from pytest import importorskip, skip
+from functools import wraps
 from warnings import catch_warnings, simplefilter
 
-from tqdm import tqdm
-from tqdm import trange
-from tqdm import TqdmDeprecationWarning, TqdmWarning
-from tqdm.std import Bar, EMA
+from pytest import importorskip, mark, raises, skip
+
+from tqdm import TqdmDeprecationWarning, TqdmWarning, tqdm, trange
 from tqdm.contrib import DummyTqdmFile
+from tqdm.std import EMA, Bar
 
 try:
     from StringIO import StringIO
 except ImportError:
     from io import StringIO
 
-from io import BytesIO
 from io import IOBase  # to support unicode strings
+from io import BytesIO
 
 
 class DeprecationError(Exception):
@@ -322,7 +320,7 @@ def test_format_meter():
 
 def test_ansi_escape_codes():
     """Test stripping of ANSI escape codes"""
-    ansi = dict(BOLD='\033[1m', RED='\033[91m', END='\033[0m')
+    ansi = {'BOLD': '\033[1m', 'RED': '\033[91m', 'END': '\033[0m'}
     desc_raw = '{BOLD}{RED}Colored{END} description'
     ncols = 123
 
@@ -798,11 +796,12 @@ def test_smoothed_dynamic_min_iters_with_min_interval():
     assert '14%' in out and '14%' in out2
 
 
+@mark.slow
 def test_rlock_creation():
     """Test that importing tqdm does not create multiprocessing objects."""
-    import multiprocessing as mp
-    if sys.version_info < (3, 3):
-        skip("unittest.mock is a 3.3+ feature")
+    mp = importorskip('multiprocessing')
+    if not hasattr(mp, 'get_context'):
+        skip("missing multiprocessing.get_context")
 
     # Use 'spawn' instead of 'fork' so that the process does not inherit any
     # globals that have been constructed by running other tests
@@ -814,8 +813,8 @@ def test_rlock_creation():
 
 def _rlock_creation_target():
     """Check that the RLock has not been constructed."""
-    from unittest.mock import patch
     import multiprocessing as mp
+    patch = importorskip('unittest.mock').patch
 
     # Patch the RLock class/method but use the original implementation
     with patch('multiprocessing.RLock', wraps=mp.RLock) as rlock_mock:
@@ -1087,10 +1086,9 @@ def test_smoothing():
     assert a2 <= c2 <= b2
 
 
+@mark.skipif(nt_and_no_colorama, reason="Windows without colorama")
 def test_deprecated_nested():
     """Test nested progress bars"""
-    if nt_and_no_colorama:
-        skip("Windows without colorama")
     # TODO: test degradation on windows without colorama?
 
     # Artificially test nested loop printing
@@ -1188,15 +1186,13 @@ def test_reset():
         assert '| 10/12' in our_file.getvalue()
 
 
+@mark.skipif(nt_and_no_colorama, reason="Windows without colorama")
 def test_position():
     """Test positioned progress bars"""
-    if nt_and_no_colorama:
-        skip("Windows without colorama")
-
     # Artificially test nested loop printing
     # Without leave
     our_file = StringIO()
-    kwargs = dict(file=our_file, miniters=1, mininterval=0, maxinterval=0)
+    kwargs = {'file': our_file, 'miniters': 1, 'mininterval': 0, 'maxinterval': 0}
     t = tqdm(total=2, desc='pos2 bar', leave=False, position=2, **kwargs)
     t.update()
     t.close()
@@ -1553,7 +1549,7 @@ def test_write():
 
 def test_len():
     """Test advance len (numpy array shape)"""
-    np = importorskip("numpy")
+    np = importorskip('numpy')
     with closing(StringIO()) as f:
         with tqdm(np.zeros((3, 4)), file=f) as t:
             assert len(t) == 3
@@ -1570,7 +1566,7 @@ def test_autodisable_disable():
 def test_autodisable_enable():
     """Test autodisable will not disable on TTY"""
     with closing(StringIO()) as our_file:
-        setattr(our_file, "isatty", lambda: True)
+        our_file.isatty = lambda: True
         with tqdm(total=10, disable=None, file=our_file) as t:
             t.update()
         assert our_file.getvalue() != ''
@@ -1585,8 +1581,8 @@ def test_TqdmDeprecationWarning():
     def test_TqdmDeprecationWarning_nofpwrite():
         raise TqdmDeprecationWarning('Test!', fp_write=None)
 
-    assert_raises(TqdmDeprecationWarning, test_TqdmDeprecationWarning)
-    assert_raises(Exception, test_TqdmDeprecationWarning_nofpwrite)
+    raises(TqdmDeprecationWarning, test_TqdmDeprecationWarning)
+    raises(Exception, test_TqdmDeprecationWarning_nofpwrite)
 
 
 def test_postfix():
@@ -1656,7 +1652,7 @@ def test_postfix_direct():
     with closing(StringIO()) as our_file:
         with tqdm(total=10, file=our_file, miniters=1, mininterval=0,
                   bar_format="{postfix[0][name]} {postfix[1]:>5.2f}",
-                  postfix=[dict(name="foo"), 42]) as t:
+                  postfix=[{'name': "foo"}, 42]) as t:
             for i in range(10):
                 if i % 2:
                     t.postfix[0]["name"] = "abcdefghij"[i]
@@ -1756,7 +1752,7 @@ def test_threading():
 def test_bool():
     """Test boolean cast"""
     def internal(our_file, disable):
-        kwargs = dict(file=our_file, disable=disable)
+        kwargs = {'file': our_file, 'disable': disable}
         with trange(10, **kwargs) as t:
             assert t
         with trange(0, **kwargs) as t:
@@ -1801,7 +1797,7 @@ def backendCheck(module):
 
 def test_auto():
     """Test auto fallback"""
-    from tqdm import autonotebook, auto
+    from tqdm import auto, autonotebook
     backendCheck(autonotebook)
     backendCheck(auto)
 
@@ -1852,8 +1848,8 @@ def test_screen_shape():
 
     # no second/third bar, leave=False
     with closing(StringIO()) as our_file:
-        kwargs = dict(file=our_file, ncols=50, nrows=2, miniters=0,
-                      mininterval=0, leave=False)
+        kwargs = {'file': our_file, 'ncols': 50, 'nrows': 2, 'miniters': 0,
+                  'mininterval': 0, 'leave': False}
         with trange(10, desc="one", **kwargs) as t1:
             with trange(10, desc="two", **kwargs) as t2:
                 with trange(10, desc="three", **kwargs) as t3:
@@ -1873,7 +1869,8 @@ def test_screen_shape():
 
     # all bars, leave=True
     with closing(StringIO()) as our_file:
-        kwargs = dict(file=our_file, ncols=50, nrows=2, miniters=0, mininterval=0)
+        kwargs = {'file': our_file, 'ncols': 50, 'nrows': 2,
+                  'miniters': 0, 'mininterval': 0}
         with trange(10, desc="one", **kwargs) as t1:
             with trange(10, desc="two", **kwargs) as t2:
                 assert "two" not in our_file.getvalue()
@@ -1895,8 +1892,8 @@ def test_screen_shape():
 
     # second bar becomes first, leave=False
     with closing(StringIO()) as our_file:
-        kwargs = dict(file=our_file, ncols=50, nrows=2, miniters=0,
-                      mininterval=0, leave=False)
+        kwargs = {'file': our_file, 'ncols': 50, 'nrows': 2, 'miniters': 0,
+                  'mininterval': 0, 'leave': False}
         t1 = tqdm(total=10, desc="one", **kwargs)
         with tqdm(total=10, desc="two", **kwargs) as t2:
             t1.update()
diff --git a/tests/tests_version.py b/tests/tests_version.py
index 3de482f85..495c797f2 100644
--- a/tests/tests_version.py
+++ b/tests/tests_version.py
@@ -1,6 +1,6 @@
 """Test `tqdm.__version__`."""
-from ast import literal_eval
 import re
+from ast import literal_eval
 
 
 def test_version():
@@ -9,5 +9,6 @@ def test_version():
     version_parts = re.split('[.-]', __version__)
     if __version__ != "UNKNOWN":
         assert 3 <= len(version_parts), "must have at least Major.minor.patch"
-        assert all([isinstance(literal_eval(i), int) for i in version_parts[:3]
-                    ]), "Version Major.minor.patch must be 3 integers"
+        assert all(
+            isinstance(literal_eval(i), int) for i in version_parts[:3]
+        ), "Version Major.minor.patch must be 3 integers"