import os
import shutil
from invoke import task, run

with open('tox.ini') as fp:
    lines = fp.read().split("\n")
    dockers = [line.split("=")[1].strip() for line in lines
               if line.find("name") != -1]


@task
def devenv(c):
    """Builds a development environment: downloads, and starts all dockers
    specified in the tox.ini file.
    """
    clean(c)
    cmd = 'tox -e devenv'
    for d in dockers:
        cmd += " --docker-dont-stop={}".format(d)
    run(cmd)


@task
def linters(c):
    """Run code linters"""
    run("tox -e linters")


@task
def all_tests(c):
    """Run all linters, and tests in redis-py. This assumes you have all
    the python versions specified in the tox.ini file.
    """
    linters(c)
    tests(c)


@task
def tests(c):
    """Run the redis-py test suite against the current python,
    with and without hiredis.
    """
    run("tox -e plain -e hiredis")


@task
def clean(c):
    """Stop all dockers, and clean up the built binaries, if generated."""
    if os.path.isdir("build"):
        shutil.rmtree("build")
    if os.path.isdir("dist"):
        shutil.rmtree("dist")
    run("docker rm -f {}".format(' '.join(dockers)))


@task
def package(c):
    """Create the python packages"""
    run("python setup.py build install")

tasks.py
import os
import shutil
from invoke import task, run

with open('tox.ini') as fp:
    lines = fp.read().split("\n")
    dockers = [line.split("=")[1].strip() for line in lines
               if line.find("name") != -1]


@task
def devenv(c):
    """Builds a development environment: downloads, and starts all dockers
    specified in the tox.ini file.
    """
    clean(c)
    cmd = 'tox -e devenv'
    for d in dockers:
        cmd += " --docker-dont-stop={}".format(d)
    run(cmd)


@task
def linters(c):
    """Run code linters"""
    run("tox -e linters")


@task
def all_tests(c):
    """Run all linters, and tests in redis-py. This assumes you have all
    the python versions specified in the tox.ini file.
    """
    linters(c)
    tests(c)


@task
def tests(c):
    """Run the redis-py test suite against the current python,
    with and without hiredis.
    """
    run("tox -e plain -e hiredis")


@task
def clean(c):
    """Stop all dockers, and clean up the built binaries, if generated."""
    if os.path.isdir("build"):
        shutil.rmtree("build")
    if os.path.isdir("dist"):
        shutil.rmtree("dist")
    run("docker rm -f {}".format(' '.join(dockers)))


@task
def package(c):
    """Create the python packages"""
    run("python setup.py sdist bdist_wheel")

tasks.py
diff --git a/.github/workflows/install_and_test.sh b/.github/workflows/install_and_test.sh
new file mode 100755
index 0000000000..330102eb41
--- /dev/null
+++ b/.github/workflows/install_and_test.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -e
+
+SUFFIX=$1
+if [ -z ${SUFFIX} ]; then
+    echo "Supply valid python package extension such as whl or tar.gz. Exiting."
+    exit 3
+fi
+
+script=`pwd`/${BASH_SOURCE[0]}
+HERE=`dirname ${script}`
+ROOT=`realpath ${HERE}/../..`
+
+cd ${ROOT}
+DESTENV=${ROOT}/.venvforinstall
+if [ -d ${DESTENV} ]; then
+    rm -rf ${DESTENV}
+fi
+python -m venv ${DESTENV}
+source ${DESTENV}/bin/activate
+pip install --upgrade --quiet pip
+pip install --quiet -r dev_requirements.txt
+invoke devenv
+invoke package
+
+# find packages
+PKG=`ls ${ROOT}/dist/*.${SUFFIX}`
+ls -l ${PKG}
+
+TESTDIR=${ROOT}/STAGETESTS
+if [ -d ${TESTDIR} ]; then
+    rm -rf ${TESTDIR}
+fi
+mkdir ${TESTDIR}
+cp -R ${ROOT}/tests ${TESTDIR}/tests
+cd ${TESTDIR}
+
+# install, run tests
+pip install ${PKG}
+pytest
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
index 4a073948cb..5384996c70 100644
--- a/.github/workflows/integration.yaml
+++ b/.github/workflows/integration.yaml
@@ -6,61 +6,65 @@ on:
       - 'docs/**'
       - '**/*.rst'
       - '**/*.md'
+    branches:
+      - master
   pull_request:
     branches:
       - master
 
 jobs:
 
-  lint:
-    name: Code linters
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - name: install python
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.9
-      - name: run code linters
-        run: |
-          pip install -r dev_requirements.txt
-          invoke linters
+   lint:
+     name: Code linters
+     runs-on: ubuntu-latest
+     steps:
+       - uses: actions/checkout@v2
+       - name: install python
+         uses: actions/setup-python@v2
+         with:
+           python-version: 3.9
+       - name: run code linters
+         run: |
+           pip install -r dev_requirements.txt
+           invoke linters
 
-  run-tests:
-    runs-on: ubuntu-latest
-    strategy:
-      max-parallel: 6
-      matrix:
-        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', 'pypy-3.7']
-    env:
-      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
-    name: Python ${{ matrix.python-version }} tests
-    steps:
-      - uses: actions/checkout@v2
-      - name: install python
-        uses: actions/setup-python@v2
-        with:
-          python-version: ${{ matrix.python-version }}
-      - name: run tests
-        run: |
-          pip install -r dev_requirements.txt
-          invoke tests
-      - name: Upload codecov coverage
-        uses: codecov/codecov-action@v2
-        with:
-          fail_ci_if_error: false
-          token: ${{ secrets.CODECOV_TOKEN }}
+   run-tests:
+     runs-on: ubuntu-latest
+     strategy:
+       max-parallel: 6
+       matrix:
+         python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', 'pypy-3.7']
+     env:
+       ACTIONS_ALLOW_UNSECURE_COMMANDS: true
+     name: Python ${{ matrix.python-version }} tests
+     steps:
+       - uses: actions/checkout@v2
+       - name: install python
+         uses: actions/setup-python@v2
+         with:
+           python-version: ${{ matrix.python-version }}
+       - name: run tests
+         run: |
+           pip install -r dev_requirements.txt
+           invoke tests
+       - name: Upload codecov coverage
+         uses: codecov/codecov-action@v2
+         with:
+           fail_ci_if_error: false
+           token: ${{ secrets.CODECOV_TOKEN }}
 
-  build_package:
+   build_and_test_package:
     name: Validate building and installing the package
     runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        extension: ['tar.gz', 'whl']
     steps:
       - uses: actions/checkout@v2
       - name: install python
         uses: actions/setup-python@v2
         with:
           python-version: 3.9
-      - name: build and install
+      - name: Run installed unit tests
         run: |
-          pip install invoke
-          invoke package
+          bash .github/workflows/install_and_test.sh ${{ matrix.extension }}
diff --git a/dev_requirements.txt b/dev_requirements.txt
index 0ca7727049..6ea50550e8 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -6,3 +6,4 @@ tox-docker==3.1.0
 invoke==1.6.0
 pytest-cov>=3.0.0
 vulture>=2.3.0
+wheel>=0.30.0
diff --git a/tasks.py b/tasks.py
index 4ca2242fa9..306291c97f 100644
--- a/tasks.py
+++ b/tasks.py
@@ -56,4 +56,4 @@ def clean(c):
 @task
 def package(c):
     """Create the python packages"""
-    run("python setup.py build install")
+    run("python setup.py sdist bdist_wheel")

CI run to install the built package
In light of bug #1645  we should amend our CI run to install the built package, in a new virtual env and run something simple like a redis.Redis().ping(). Eventually we could build up to running the full integration test against the package.
CI run to install the built package
In light of bug #1645  we should amend our CI run to install the built package, in a new virtual env and run something simple like a redis.Redis().ping(). Eventually we could build up to running the full integration test against the package.
