Repository: slaclab/pydm (language: python)

Issue:
BLD: pin pyqt to 5.x
Remove the `<5.15` pin and just ensure we're on PyQt 5.x.

This is just a test and may fail miserably. Let's see what happens on CI.


Discussion / hints:
## [Codecov](https://app.codecov.io/gh/slaclab/pydm/pull/999?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab) Report
> Merging [#999](https://app.codecov.io/gh/slaclab/pydm/pull/999?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab) (13e5c1e) into [master](https://app.codecov.io/gh/slaclab/pydm/commit/504e084ee82af26c910ea1bf7bdf2b0ce7e4ed70?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab) (504e084) will **decrease** coverage by `0.01%`.
> The diff coverage is `n/a`.

:exclamation: Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please [install the Github App Integration](https://github.com/apps/codecov) for your organization. [Read more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab).

```diff
@@            Coverage Diff             @@
##           master     #999      +/-   ##
==========================================
- Coverage   59.21%   59.20%   -0.01%     
==========================================
  Files         105      105              
  Lines       14054    14054              
==========================================
- Hits         8322     8321       -1     
- Misses       5732     5733       +1     
```


[see 1 file with indirect coverage changes](https://app.codecov.io/gh/slaclab/pydm/pull/999/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab)


So it pretty much works, according to CI testing. A quick double-check indicates that 5.15 is getting installed there:
```
pyqt                      5.15.7           py38ha0d8c90_3    conda-forge
```

On the PCDS side, we have a desire to use 5.15 for our general runtime/data acquisition environment. As we all know at this point, if you want Qt Designer to work with PyDM plugins, you have to pin to another specific version due to conda-forge's removal of a necessary shared library in recent releases. For that, we intend to keep a designer-only environment available that allows screen development to continue. 

So, if PyDM's `<5.15` pinning is relaxed to `=5`, we'll be free to adjust our pyqt pinning at the environment level.

I'll mark this as ready for review to get input. Would this PR or the above environment creation methodology be problematic for the accelerator (or other PyDM users)?
My thoughts are:

- Is there any reason to keep the pin in order to make sure people get the `pyqt` builds with proper `designer` support?
- After removing this pin, do we need to test against multiple versions of `pyqt` to make sure we don't stumble into regressions for people who are running the older versions with proper `designer` support?

Otherwise, I agree with the change. Extraneous upper pins cause more problems than they solve.
> Would this PR or the above environment creation methodology be problematic for the accelerator (or other PyDM users)?

> Is there any reason to keep the pin in order to make sure people get the pyqt builds with proper designer support?

There would be no issue on the accelerator side, we can pin at the environment level as well. And the workaround of just copying over the `libpyqt5.so` file as mentioned near the end of #866 still works for now at least, though not exactly robust.

Maybe a note added to our installation documentation with the command for creating a designer compatible environment would be helpful, especially for other pydm users as well. (I could add as a followup if you'd like)

> After removing this pin, do we need to test against multiple versions of pyqt to make sure we don't stumble into regressions for people who are running the older versions with proper designer support?

This sounds like a good idea. I want to take a look at our pipelines already (maybe switching to mamba at least to fix the long build issues, maybe move to github actions entirely). Adding old and new pyqt could be done there as well.

Overall though I agree with the change as well, gives users the choice whether to pin or not as needed.

Fix patch:
diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
new file mode 100644
index 000000000..055b8598a
--- /dev/null
+++ b/.github/workflows/gh-pages.yml
@@ -0,0 +1,37 @@
+# This workflow will publish a new version of the documentation to the gh-pages branch
+
+name: Publish Documentation
+
+on:
+  push:
+    branches: [ "master" ]
+
+jobs:
+  deploy:
+    if: ${{ github.repository == 'slaclab/pydm' }}
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: conda-incubator/setup-miniconda@v2
+        with:
+          python-version: 3.9
+          mamba-version: "*"
+          channels: conda-forge
+          activate-environment: pydm-docs
+
+      - name: Install python packages
+        shell: bash -el {0}
+        run: |
+          mamba install pydm $(cat docs-requirements.txt)
+      - name: Build Docs
+        shell: bash -l {0}
+        run: |
+          pushd docs
+          make html
+          popd
+
+      - name: Deploy to gh-pages
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: docs/build/html
diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
new file mode 100644
index 000000000..f25376d95
--- /dev/null
+++ b/.github/workflows/publish-to-pypi.yml
@@ -0,0 +1,52 @@
+# This workflow will run when a new release is published and upload it to PyPI using trusted publishing.
+# Done in two separate build and publish jobs per suggested best practice for limiting token usage.
+
+name: Publish - PyPI
+
+on:
+  release:
+    types:
+      - published
+
+jobs:
+  build:
+    name: Build new release
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout Repository
+      uses: actions/checkout@v3
+
+    - name: Set up Python
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.x'
+
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install setuptools wheel
+    - name: Build Package
+      run: python setup.py sdist
+
+    - name: Upload package
+      uses: actions/upload-artifact@v3
+      with:
+        name: package
+        path: dist/*
+        retention-days: 1
+
+  publish:
+    name: Publish release to PyPI
+    runs-on: ubuntu-latest
+    permissions:
+      id-token: write # Used for trusted publishing
+    steps:
+      - name: Download package
+        uses: actions/download-artifact@v3
+        with:
+          name: package
+          path: dist/
+
+      - name: Publish package distributions to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/README.md b/README.md
index 9421afd2f..96b4dce10 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-[![Build Status](https://dev.azure.com/pydm/pydm/_apis/build/status/slaclab.pydm?branchName=master)](https://dev.azure.com/pydm/pydm/_build/latest?definitionId=1&branchName=master)
-[![codecov](https://codecov.io/gh/slaclab/pydm/branch/master/graph/badge.svg)](https://codecov.io/gh/slaclab/pydm)
+[![Build Status](https://github.com/slaclab/pydm/actions/workflows/run-tests.yml/badge.svg?branch=master)](https://github.com/slaclab/pydm/actions/workflows/run-tests.yml)
 
 <p align="center">
   <h1 align="center">PyDM: Python Display Manager</h1>
@@ -31,7 +30,7 @@ as the abstraction layer for the Qt Python wrappers (PyQt5/PyQt4/PySide2/PySide)
 **All tests are performed with PyQt5**.
 
 # Prerequisites
-* Python 3.6+
+* Python 3.7+
 * Qt 5.6 or higher
 * qtpy
 * PyQt5 >= 5.7 or any other Qt Python wrapper.
diff --git a/azure-build-template.yml b/azure-build-template.yml
deleted file mode 100644
index 868ab7133..000000000
--- a/azure-build-template.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-
-jobs:
-- job: 'Build'
-  pool:
-    vmImage: 'ubuntu-22.04'
-  variables:
-    PYTHON: 3.7.3
-    BUILD_DOCS: 1
-
-  steps:
-  - bash: echo "##vso[task.prependpath]$CONDA/bin"
-    displayName: 'Linux - Add conda to PATH'
-    condition: eq(variables['agent.os'], 'Linux' )
-
-  # Linux
-  - bash: |
-      # Fix Anaconda permissions
-      sudo install -d -m 0777 /usr/share/miniconda/
-      sudo install -d -m 0777 /usr/share/miniconda/envs
-    displayName: 'Linux - Prepare OS'
-    condition: eq(variables['agent.os'], 'Linux' )
-
-  - bash: |
-      conda config --set always_yes yes
-      conda config --set channel_priority strict
-      conda config --set show_channel_urls yes
-      conda config --add channels conda-forge
-    displayName: 'Anaconda - Configure'
-
-  - bash: |
-     conda create --name build-environment python=$(PYTHON) conda-build anaconda-client
-    displayName: 'Anaconda - Create'
-
-  - bash: |
-      source activate build-environment
-      conda build conda-recipe --output-folder bld-dir
-    displayName: 'Anaconda - Build PyDM Package'
-
-  - task: PublishBuildArtifacts@1
-    displayName: 'Publish - Anaconda Package'
-    inputs:
-      PathtoPublish: 'bld-dir'
-      ArtifactName: 'anaconda_package'
-
-  - bash: |
-      conda config --add channels 'file:///$(Build.Repository.LocalPath)/bld-dir'
-      source activate build-environment
-      conda install pydm
-    displayName: 'Anaconda - Configure - Add local bld-dir'
-
-  - bash: |
-      source activate build-environment
-      pip install -r docs-requirements.txt
-      pushd docs
-      make html
-      popd
-    displayName: 'Documentation - Build HTML'
-    condition: |
-      and(
-        and(
-          eq(variables['agent.os'], 'Linux' ),
-          eq(variables['BUILD_DOCS'], 1)
-        ),
-        succeeded()
-      )
-
-  - task: PublishBuildArtifacts@1
-    displayName: 'Publish - Documentation'
-    inputs:
-      PathtoPublish: 'docs/build/html/'
-      ArtifactName: 'docs_html'
-    condition: |
-      and(
-        and(
-          eq(variables['agent.os'], 'Linux' ),
-          eq(variables['BUILD_DOCS'], 1)
-        ),
-        succeeded()
-      )
diff --git a/azure-pipelines-linux.yml b/azure-pipelines-linux.yml
deleted file mode 100644
index 9fd2f81d0..000000000
--- a/azure-pipelines-linux.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-jobs:
-  # Linux
-  - template: azure-test-template.yml
-    parameters:
-      name: Linux_3_7
-      vmImage: 'Ubuntu 20.04'
-      build_docs: 1
-      python: '3.7'
-      allowFailure: false
-  - template: azure-test-template.yml
-    parameters:
-      name: Linux_3_8
-      vmImage: 'Ubuntu 20.04'
-      build_docs: 1
-      python: '3.8'
-      allowFailure: false
-  - template: azure-test-template.yml
-    parameters:
-      name: Linux_3_9
-      vmImage: 'Ubuntu 20.04'
-      build_docs: 1
-      python: '3.9'
-      allowFailure: true
diff --git a/azure-pipelines-macos.yml b/azure-pipelines-macos.yml
deleted file mode 100644
index 0df2dbdc8..000000000
--- a/azure-pipelines-macos.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-jobs:
-  # MacOS
-  - template: azure-test-template.yml
-    parameters:
-      name: MacOS_3_7
-      vmImage: 'macos-12'
-      python: '3.7'
-      allowFailure: false
-  - template: azure-test-template.yml
-    parameters:
-      name: MacOS_3_8
-      vmImage: 'macos-12'
-      python: '3.8'
-      allowFailure: false
-  - template: azure-test-template.yml
-    parameters:
-      name: MacOS_3_9
-      vmImage: 'macos-12'
-      python: '3.9'
-      allowFailure: true
diff --git a/azure-pipelines-windows.yml b/azure-pipelines-windows.yml
deleted file mode 100644
index 3b0a1175f..000000000
--- a/azure-pipelines-windows.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-jobs:
-  # Windows
-  - template: azure-test-template-win.yml
-    parameters:
-      name: Windows_3_7
-      vmImage: 'windows-2022'
-      build_docs: 1
-      python: '3.7'
-      python_name: '3_7'
-      allowFailure: true
-  - template: azure-test-template-win.yml
-    parameters:
-      name: Windows_3_8
-      vmImage: 'windows-2022'
-      build_docs: 1
-      python: '3.8'
-      python_name: '3_8'
-      allowFailure: true
-  - template: azure-test-template-win.yml
-    parameters:
-      name: Windows_3_9
-      vmImage: 'windows-2022'
-      build_docs: 1
-      python: '3.9'
-      python_name: '3_9'
-      allowFailure: true
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 19f58d44b..000000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,224 +0,0 @@
-############################################################################################
-#  This Azure Pipelines was created after much time expend exploring the documentation
-#  available online.
-#
-#  Use it at your own risk.
-#
-#  Here is how it works:
-#  - We configure it to run for any branch but only for PRs to master.
-#  - The OFFICIAL_REPO variable avoids issues with Forks.
-#  - The DeployControl job is responsible for defining a variable that is used
-#  by the other deployment jobs to ensure that we only deploy for the OFFICIAL_REPO
-#  and not for PRs.
-#  - We currently use the template to run the tests (See azure-test-template.yml).
-#  - After the tests pass 3 Publish jobs go into action:
-#      - Documentation
-#        This job is executed on commits to master and tags.
-#        It deploys the new docs to the gh-pages branch at GitHub.
-#      - PyPI
-#        This job is executed on tags only.
-#        It deploys the package to the Python Package Index.
-#      - Anaconda
-#        This job is executed on commits to master and tags.
-#        It deploys the package to the DEV or TAG channels at Anaconda Cloud.
-############################################################################################
-
-trigger:
-  branches:
-    include:
-    - '*' # Build for all branches if they have a azure-pipelines.yml file.
-  tags:
-    include:
-    - 'v*' # Ensure that we are building for tags starting with 'v' (Official Versions)
-
-# Build only for PRs for master branch
-pr:
-  autoCancel: true
-  branches:
-    include:
-      - master
-
-variables:
-  OFFICIAL_REPO: 'slaclab/pydm'
-
-stages:
-  - stage: "Linter"
-    dependsOn: []
-    jobs:
-      - job: 'Flake8'
-        continueOnError: true
-        displayName: 'Linter - Flake8'
-        pool:
-          vmImage: 'ubuntu-latest'
-        steps:
-        - task: UsePythonVersion@0
-          inputs:
-            versionSpec: '3.x'
-            architecture: 'x64'
-        - script: pip install flake8
-          displayName: 'Python - Install flake8'
-        - bash: |
-            flake8 --max-line-length=120 --exit-zero pydm
-          displayName: 'Flake8'
-  - stage: "Build"
-    dependsOn: []
-    jobs:
-      # Build the conda package and docs
-      - template: azure-build-template.yml
-  - stage: "Tests"
-    dependsOn:
-      - "Build"
-    jobs:
-      - template: azure-pipelines-linux.yml
-      - template: azure-pipelines-macos.yml
-      - template: azure-pipelines-windows.yml
-  - stage: "Deploy"
-    dependsOn:
-      - "Linter"
-      - "Tests"
-    jobs:
-      - job: 'Control'
-        displayName: Build Control
-        pool:
-          vmImage: 'ubuntu-latest'
-        steps:
-          - bash: echo "##vso[task.setvariable variable=build_ok;isOutput=true]true"
-            name: var
-            displayName: "Control - Set Allowed Flag"
-            condition: |
-              and
-              (
-                eq(variables['Build.Repository.Name'], variables['OFFICIAL_REPO']),
-                eq(variables['System.PullRequest.PullRequestNumber'], variables['NULL'])
-              )
-      - job: 'Publish_Documentation'
-        dependsOn: Control
-        displayName: Publish - Documentation
-        pool:
-          vmImage: 'ubuntu-latest'
-        condition: |
-            and (
-              eq( dependencies.Control.outputs['var.build_ok'], true),
-              or (
-                contains(variables['Build.SourceBranch'],'heads/master'),
-                contains(variables['Build.SourceBranch'],'refs/tags')
-              )
-            )
-        steps:
-        - checkout: self
-          persistCredentials: true
-          clean: true
-        - task: DownloadBuildArtifacts@0
-          inputs:
-            buildType: 'current'
-            artifactName: 'docs_html'
-            downloadPath: $(Build.Repository.LocalPath)/artifacts
-          displayName: Artifact Download - Documentation
-
-        - bash: |
-            git config --global user.name "Azure Pipelines"
-            git checkout gh-pages
-            cp -r $(Build.Repository.LocalPath)/artifacts/docs_html/* .
-            rm -rf $(Build.Repository.LocalPath)/artifacts
-            git add *
-            git commit -m "Updating Docs with latest build"
-            git push https://$(GIT_PAT)@github.com/$(Build.Repository.Name).git gh-pages
-          displayName: Documentation - Upload
-
-      - job: 'Publish_PyPI'
-        dependsOn: Control
-        displayName: Publish - PyPI
-        condition: |
-          and (
-            eq( dependencies.Control.outputs['var.build_ok'], true),
-            contains(variables['Build.SourceBranch'],'refs/tags')
-          )
-
-        pool:
-          vmImage: 'ubuntu-latest'
-
-        steps:
-        - task: UsePythonVersion@0
-          inputs:
-            versionSpec: '3.x'
-            architecture: 'x64'
-
-        - script: python setup.py sdist
-          displayName: 'Python - Build sdist'
-
-        - task: PublishBuildArtifacts@1
-          displayName: 'Publish - Python Package'
-          inputs:
-            pathtoPublish: 'dist'
-            artifactName: 'dist'
-
-        - bash: |
-            pip install twine
-          displayName: PyPI - Install Twine
-
-        - task: TwineAuthenticate@0
-          inputs:
-            externalFeeds: 'pypi'
-          displayName: PyPI - Authenticate
-
-        # Download PyPI package
-        - task: DownloadBuildArtifacts@0
-          inputs:
-            buildType: 'current'
-            artifactName: 'dist'
-            downloadPath: $(Build.Repository.LocalPath)/artifacts
-          displayName: Artifact Download - PyPI Package
-
-        - bash: |
-            twine upload --config-file $(PYPIRC_PATH) $(Build.Repository.LocalPath)/artifacts/dist/*
-          condition: contains(variables['Build.SourceBranch'], 'tags')
-          displayName: PyPI - Upload
-
-      - job: 'Publish_Anaconda'
-        dependsOn: Control
-        displayName: Publish - Anaconda
-        condition: |
-          and (
-            eq( dependencies.Control.outputs['var.build_ok'], true),
-            or (
-              contains(variables['Build.SourceBranch'],'heads/master'),
-              contains(variables['Build.SourceBranch'],'refs/tags')
-            )
-          )
-        pool:
-          vmImage: 'ubuntu-latest'
-        steps:
-        # Download Anaconda packages
-        - task: DownloadBuildArtifacts@0
-          inputs:
-            buildType: 'current'
-            artifactName: 'anaconda_package'
-            downloadPath: $(Build.Repository.LocalPath)/artifacts
-          displayName: Artifact Download - Anaconda Packages
-
-        - bash: |
-            # Fix Anaconda permissions
-            sudo install -d -m 0777 /usr/envs
-            sudo install -d -m 0777 /usr/share/miniconda/
-
-          displayName: 'Linux - Prepare OS'
-          condition: eq(variables['agent.os'], 'Linux' )
-
-        - task: CondaEnvironment@0
-          displayName: 'Anaconda - Create - Upload Environment'
-          inputs:
-            environmentName: 'test-environment'
-            packageSpecs: 'python=3.6 conda-build conda-verify anaconda-client'
-            updateConda: true
-
-        - script: 'anaconda upload $(Build.Repository.LocalPath)/artifacts/anaconda_package/*/*.tar.bz2'
-          displayName: 'Anaconda - Upload - DEV'
-          env:
-            ANACONDA_API_TOKEN: $(CONDA_UPLOAD_TOKEN_DEV)
-          condition: contains(variables['Build.SourceBranch'],'heads/master')
-
-        - script: 'anaconda upload $(Build.Repository.LocalPath)/artifacts/anaconda_package/*/*.tar.bz2'
-          displayName: 'Anaconda - Upload - TAG'
-          env:
-            ANACONDA_API_TOKEN: $(CONDA_UPLOAD_TOKEN_TAG)
-          condition: contains(variables['Build.SourceBranch'],'refs/tags')
diff --git a/docs-requirements.txt b/docs-requirements.txt
index c70884264..bc6bcabd0 100644
--- a/docs-requirements.txt
+++ b/docs-requirements.txt
@@ -1,4 +1,4 @@
 doctr
-sphinx
+sphinx==5.3.0
 sphinx_rtd_theme
 sphinxcontrib-napoleon
diff --git a/examples/positioner/positioner_ioc.py b/examples/positioner/positioner_ioc.py
index 384242c84..906039e94 100644
--- a/examples/positioner/positioner_ioc.py
+++ b/examples/positioner/positioner_ioc.py
@@ -65,6 +65,6 @@ def __init__(self):
 	server = SimpleServer()
 	server.createPV(prefix, pvdb)
 	driver = myDriver()
-	print "Server is running... (ctrl+c to close)"
+	print("Server is running... (ctrl+c to close)")
 	while True:
 		server.process(0.1)


Test patch:
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
new file mode 100644
index 000000000..5fcc405cd
--- /dev/null
+++ b/.github/workflows/run-tests.yml
@@ -0,0 +1,67 @@
+# This workflow will install pydm dependencies, lint with flake8, and run the test suite, for all combinations
+# of operating systems and version numbers specified in the matrix
+
+name: Build Status
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest, windows-latest, macos-latest]
+        python-version: [3.7, 3.8, 3.9]
+        pyqt-version: [5.12.3, 5.15.7]
+    env:
+      DISPLAY: ':99.0'
+      QT_MAC_WANTS_LAYER: 1  # PyQT gui tests involving qtbot interaction on macOS will fail without this
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Setup conda
+      uses: conda-incubator/setup-miniconda@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+        mamba-version: "*"
+        channels: conda-forge
+        activate-environment: pydm-env
+    - name: Install python packages
+      shell: bash -el {0}
+      run: |
+        if [ "$RUNNER_OS" == "Windows" ]; then
+          mamba install flake8 pyqt=${{ matrix.pyqt-version }} 
+          mamba install --file requirements.txt 
+          mamba install --file windows-dev-requirements.txt
+        else
+          mamba install flake8 pyqt=${{ matrix.pyqt-version }} $(cat requirements.txt dev-requirements.txt)
+        fi
+    - name: Install packages for testing a pyqt app on linux
+      shell: bash -el {0}
+      run: |
+        if [ "$RUNNER_OS" == "Linux" ]; then
+          sudo apt install xvfb herbstluftwm libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils 
+          sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset
+          sleep 3
+          sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_herbstluftwm_99.pid --make-pidfile --background --exec /usr/bin/herbstluftwm
+          sleep 1
+        fi
+    - name: Lint with flake8
+      shell: bash -el {0}
+      run: |
+        # stop the build if there are Python syntax errors or undefined names
+        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+        # exit-zero treats all errors as warnings.
+        flake8 . --count --exit-zero --max-line-length=120 --statistics
+    - name: Test with pytest
+      shell: bash -el {0}
+      run: |
+        python run_tests.py
diff --git a/azure-test-template-win.yml b/azure-test-template-win.yml
deleted file mode 100644
index d5962c5ae..000000000
--- a/azure-test-template-win.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-# Azure Pipelines CI job template for PyDM Tests
-
-parameters:
-  name: ''
-  vmImage: ''
-  python: ''
-  python_name: ''
-  allowFailure: false
-
-
-jobs:
-- job: ${{ parameters.name }}
-  pool:
-    vmImage: ${{ parameters.vmImage }}
-  variables:
-    python: ${{ parameters.python }}
-    python_name: ${{ parameters.python_name }}
-  continueOnError: ${{ parameters.allowFailure }}
-
-  steps:
-  - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts"
-    displayName: 'Windows - Add conda to PATH'
-    condition: eq(variables['agent.os'], 'Windows_NT' )
-
-  - bash: |
-      conda config --set always_yes yes
-      conda config --set channel_priority strict
-      conda config --set show_channel_urls yes
-      conda config --add channels conda-forge
-    displayName: 'Anaconda - Configure'
-
-  - script: |
-      echo Python Version $(python)
-      conda create --yes --quiet --name test-environment-$(python_name) python=$(python)
-    displayName: 'Anaconda - Create'
-
-  - bash: |
-      conda config --add channels 'file:///$(Build.Repository.LocalPath)/anaconda_package'
-    displayName: 'Anaconda - Configure - Add local bld-dir'
-
-  - task: DownloadBuildArtifacts@0
-    inputs:
-      buildType: 'current'
-      artifactName: 'anaconda_package'
-      downloadPath: $(Build.Repository.LocalPath)/
-    displayName: Artifact Download - Anaconda Packages
-
-  - script: |
-      call activate test-environment-$(python_name)
-      conda install python=$(python) pytest-azurepipelines pydm --file windows-dev-requirements.txt --update-deps
-    displayName: 'Anaconda - Install Dependencies'
-
-  - script: |
-      call activate test-environment-$(python_name)
-      conda info
-      conda list
-      python -c "from PyQt5 import QtCore; print(QtCore)"
-    displayName: 'Debug - Conda List'
-    continueOnError: false
-
-  - script: |
-      call activate test-environment-$(python_name)
-      python run_tests.py --timeout 30 --show-cov --test-run-title="Tests for $(Agent.OS) - Python $(python)" --napoleon-docstrings
-    displayName: 'Tests - Run'
-    continueOnError: false
-
-  - script: |
-      call activate test-environment-$(python_name)
-      codecov
-    displayName: 'Codecov - Upload'
-    continueOnError: true
diff --git a/azure-test-template.yml b/azure-test-template.yml
deleted file mode 100644
index 196f5743d..000000000
--- a/azure-test-template.yml
+++ /dev/null
@@ -1,122 +0,0 @@
-# Azure Pipelines CI job template for PyDM Tests
-
-parameters:
-  name: ''
-  vmImage: ''
-  python: ''
-  allowFailure: false
-
-jobs:
-  - job: ${{ parameters.name }}
-    pool:
-      vmImage: ${{ parameters.vmImage }}
-    variables:
-      python: ${{ parameters.python }}
-    continueOnError: ${{ parameters.allowFailure }}
-
-    steps:
-    - bash: echo "##vso[task.prependpath]$CONDA/bin"
-      displayName: 'MacOS - Add conda to PATH'
-      condition: eq(variables['agent.os'], 'Darwin' )
-
-    - bash: echo "##vso[task.prependpath]$CONDA/bin"
-      displayName: 'Linux - Add conda to PATH'
-      condition: eq(variables['agent.os'], 'Linux' )
-
-    # Linux
-    - bash: |
-        # Install & Start Windows Manager for Linux
-        sudo apt-get install -y xvfb herbstluftwm
-        sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset
-        sleep 3
-
-        # Fix Anaconda permissions
-        sudo install -d -m 0777 /usr/share/miniconda/
-        sudo install -d -m 0777 /usr/share/miniconda/envs
-
-      displayName: 'Linux - Prepare OS'
-      condition: eq(variables['agent.os'], 'Linux' )
-
-    # Linux
-    - bash: |
-        sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_herbstluftwm_99.pid --make-pidfile --background --exec /usr/bin/herbstluftwm
-        sleep 1
-      env:
-        DISPLAY: :99.0
-      displayName: 'Linux - Start herbstluftwm'
-      condition: eq(variables['agent.os'], 'Linux' )
-
-    # MacOS
-    - bash: |
-       # Fix Anaconda permissions
-       sudo chown -R $USER $CONDA
-      displayName: 'MacOS - Prepare OS'
-      condition: eq(variables['agent.os'], 'Darwin' )
-
-    - bash: |
-        conda config --set always_yes yes
-        conda config --set channel_priority strict
-        conda config --set show_channel_urls yes
-        conda config --add channels conda-forge
-      displayName: 'Anaconda - Configure'
-
-    - bash: |
-       conda create --name test-environment-$(python) python=$(python)
-      displayName: 'Anaconda - Create'
-
-    - bash: |
-        conda config --add channels 'file:///$(Build.Repository.LocalPath)/anaconda_package'
-      displayName: 'Anaconda - Configure - Add local bld-dir'
-
-    - bash: |
-        conda info
-        conda info --envs
-        conda config --show channels
-      displayName: Debug
-
-    - task: DownloadBuildArtifacts@0
-      inputs:
-        buildType: 'current'
-        artifactName: 'anaconda_package'
-        downloadPath: $(Build.Repository.LocalPath)/
-      displayName: Artifact Download - Anaconda Packages
-
-    - bash: |
-        source activate test-environment-$(python)
-        conda install python=$(python) pytest-azurepipelines pydm --file dev-requirements.txt
-      displayName: 'Anaconda - Install Dependencies'
-
-    # mac os with python 3.7 was not solving the environment correctly (using a too old version of pyqt).
-    # This will force it to use a working version
-    - bash: |
-        source activate test-environment-$(python)
-        conda install pyqt==5.12.3
-      displayName: 'Anaconda - mac os python 3.7 pyqt update'
-      condition: eq('${{ parameters.name }}', 'MacOS_3_7')
-
-    - bash: |
-        source activate test-environment-$(python)
-        # debug of conda environment
-        conda info
-        conda list
-        echo python location: `which python`
-        echo pytest location: `which pytest`
-        python -c "from PyQt5 import QtCore; print(QtCore)"
-      displayName: 'Debug - Conda List'
-      continueOnError: false
-
-    - bash: |
-        source activate test-environment-$(python)
-        python run_tests.py --timeout 30 --show-cov --test-run-title="Tests for $(Agent.OS) - Python $(python)" --napoleon-docstrings
-      displayName: 'Tests - Run'
-      continueOnError: false
-      env:
-        # Setting this fixes an issue with macOS 11+ and pyqt
-        QT_MAC_WANTS_LAYER: 1
-        DISPLAY: :99.0
-
-    - bash: |
-        source activate test-environmnet-$(python)
-        bash <(curl -s https://codecov.io/bash)
-      displayName: 'Codecov - Upload'
-      continueOnError: true
diff --git a/pydm/tests/widgets/test_lineedit.py b/pydm/tests/widgets/test_lineedit.py
index 62a432066..23a0f51ac 100644
--- a/pydm/tests/widgets/test_lineedit.py
+++ b/pydm/tests/widgets/test_lineedit.py
@@ -535,8 +535,9 @@ def test_set_display(qtbot, qapp, value, has_focus, channel_type, display_format
     pydm_lineedit.check_enable_state()
 
     if has_focus:
-        pydm_lineedit.setFocus()
         def wait_focus():
+            pydm_lineedit.setFocus()
+            qapp.processEvents()
             return pydm_lineedit.hasFocus()
 
         qtbot.waitUntil(wait_focus, timeout=5000)
@@ -590,6 +591,8 @@ def test_focus_in_event(qtbot, qapp, displayed_value, focus_reason, expected_foc
 
     def wait_focus(focus_state):
         """ Verify the current focus state of the line edit """
+        if focus_state:
+            pydm_lineedit.setFocus(Qt.OtherFocusReason)
         qapp.processEvents()
         return pydm_lineedit.hasFocus() == focus_state
 
@@ -644,9 +647,8 @@ def test_focus_out_event(qtbot, qapp, display_value):
     pydm_lineedit.check_enable_state()
 
     pydm_lineedit._display = display_value
-    pydm_lineedit.setFocus()
-    qapp.processEvents()
     def wait_focus():
+        pydm_lineedit.setFocus()
         qapp.processEvents()
         return pydm_lineedit.hasFocus()
 
diff --git a/pydm/tests/widgets/test_shell_command.py b/pydm/tests/widgets/test_shell_command.py
index 74fa4f1ec..57356b09f 100644
--- a/pydm/tests/widgets/test_shell_command.py
+++ b/pydm/tests/widgets/test_shell_command.py
@@ -228,16 +228,13 @@ def test_env_var(qtbot):
     pydm_shell_command = PyDMShellCommand()
     qtbot.addWidget(pydm_shell_command)
 
-    cmd = "echo Test: $PATH"
+    os.environ["PYDM_TEST_ENV_VAR"] = "this is a pydm test"
+    cmd = "echo Test: $PYDM_TEST_ENV_VAR"
     if platform.system() == 'Windows':
-        cmd = "echo Test: %PATH%"
+        cmd = "echo Test: %PYDM_TEST_ENV_VAR%"
 
     pydm_shell_command.commands = [cmd]
     qtbot.mouseClick(pydm_shell_command, QtCore.Qt.LeftButton)
     stdout, stderr = pydm_shell_command.process.communicate()
     assert pydm_shell_command.process.returncode == 0
-    if platform.system() == 'Windows':
-        # Windows changes C:\\ to C:\\\\
-        assert os.getenv("PATH") in str(stdout).replace('\\\\', '\\')
-    else:
-        assert "Test: {}".format(os.getenv("PATH")) in str(stdout)
+    assert stdout.decode('utf-8') == "Test: this is a pydm test\n"