What changed, and why it matters
This commit adds a new GitHub Actions configuration file that automatically runs code-quality checks and unit tests when code is pushed or a pull request is opened. It does not change any application code, user-facing behavior, or security-sensitive logic. It is purely an infrastructure change to the project's continuous integration (CI) system.
No security action required. Review the workflow as a normal CI/infrastructure change. Ensure repository secrets (COVERALLS_REPO_TOKEN) are configured with minimal scope and that action pin hashes correspond to trusted releases.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces .github/workflows/tests.yml, replacing previous Cirrus CI tasks. The workflow defines three jobs: flake8-mandatory linting, a ban-unicode linter, and two pytest unittest matrices (latest and frozen dependencies) across Python 3.10–3.14. It pins third-party action hashes, sets read-only workflow permissions, and uses repository secrets only for optional Coveralls upload. No source code in the Electrum application is modified.
Changed components
.github/workflows/tests.ymlInspect captured patch +183 / −0
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..43f5fbb
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,183 @@
+name: tests
+
+on:
+ push:
+ branches: [master]
+ tags: ['*']
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
+
+permissions:
+ contents: read
+
+jobs:
+ flake8-mandatory:
+ name: "linter: Flake8 Mandatory"
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - name: Set up Python
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+ with:
+ python-version: "3.10"
+ cache: 'pip'
+ - name: Install flake8
+ run: pip install "flake8==7.3.0" "flake8-bugbear==25.10.21"
+ - name: Run flake8
+ # list of error codes:
+ # - https://flake8.pycqa.org/en/latest/user/error-codes.html
+ # - https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes
+ # - https://github.com/PyCQA/flake8-bugbear/tree/8c0e7eb04217494d48d0ab093bf5b31db0921989#list-of-warnings
+ run: |
+ flake8 . --count \
+ --select="E9,E101,E129,E273,E274,E703,E71,E722,F5,F6,F7,F8,W191,W29,B,B909" \
+ --ignore="B007,B009,B010,B036,B042,F541,F841" \
+ --show-source --statistics \
+ --exclude "*_pb2.py,electrum/_vendor/"
+
+ ban-unicode:
+ name: "linter: ban unicode"
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - name: Set up Python
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+ with:
+ python-version: "3.10"
+ - name: Run ban_unicode
+ run: ./contrib/ban_unicode.py
+
+ # unittests using the 'latest' runtime python-dependencies
+ unittests:
+ name: "unittests: py${{ matrix.python }}${{ matrix.debug && ', debug-mode' || '' }}"
+ runs-on: ubuntu-24.04
+ needs: [flake8-mandatory]
+ strategy:
+ fail-fast: false
+ matrix:
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
+ debug: [false]
+ include:
+ - python: "3.14"
+ debug: true
+ env:
+ LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/
+ PYTHONASYNCIODEBUG: ${{ matrix.debug && '1' || '' }}
+ PYTHONDEVMODE: ${{ matrix.debug && '1' || '' }}
+ ELECTRUM_ECC_DONT_COMPILE: "1"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0 # full clone for coveralls
+ - name: Set up Python
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+ with:
+ python-version: ${{ matrix.python }}
+ cache: 'pip'
+ cache-dependency-path: |
+ contrib/requirements/requirements-ci.txt
+ contrib/requirements/requirements.txt
+ - name: Cache libsecp256k1
+ id: cache-libsecp
+ uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: contrib/_saved_secp256k1_build
+ key: libsecp-${{ runner.os }}-${{ hashFiles('contrib/make_libsecp256k1.sh') }}
+ - name: Build libsecp256k1
+ if: steps.cache-libsecp.outputs.cache-hit != 'true'
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install automake libtool
+ ./contrib/make_libsecp256k1.sh
+ mkdir -p contrib/_saved_secp256k1_build
+ cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/
+ - name: Install Qt/QML runtime deps
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libgl1 libegl1 libxkbcommon0 libdbus-1-3
+ - name: Install Python dependencies
+ run: |
+ pip install -r contrib/requirements/requirements-ci.txt
+ pip install ".[tests,qml_gui]"
+ - name: Log versions
+ run: python3 --version && pip freeze --all
+ - name: Run pytest with coverage
+ run: |
+ coverage run --source=electrum \
+ "--omit=electrum/gui/*,electrum/plugins/*,electrum/scripts/*" \
+ -m pytest tests -v
+ coverage report
+ - name: Upload to Coveralls
+ if: matrix.python == '3.10' && !matrix.debug
+ env:
+ # 'COVERALLS_REPO_TOKEN' needs to be set in the GitHub repository settings
+ COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
+ CI_NAME: github-actions
+ CI_BUILD_NUMBER: ${{ github.run_id }}
+ CI_JOB_ID: ${{ github.job }}-${{ github.run_attempt }}
+ CI_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ CI_BRANCH: ${{ github.ref_name }}
+ CI_PULL_REQUEST: ${{ github.event.pull_request.number }}
+ # the repo token will be empty when pull requests from forks get opened
+ # so we won't upload on every pull request, but it will run again
+ # with the token once the PR gets merged.
+ run: if [ -n "$COVERALLS_REPO_TOKEN" ]; then coveralls; fi
+
+ # unittests using the ~same frozen dependencies that are used in the released binaries
+ # note: not using pinned pyqt here, due to "qml_gui" extra
+ unittests-frozen:
+ name: "unittests: py3.10, frozen-deps"
+ runs-on: ubuntu-24.04
+ needs: [flake8-mandatory]
+ env:
+ LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/
+ ELECTRUM_ECC_DONT_COMPILE: "1"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - name: Set up Python
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
+ with:
+ python-version: "3.10"
+ cache: 'pip'
+ cache-dependency-path: |
+ contrib/requirements/requirements-ci.txt
+ contrib/requirements/requirements.txt
+ contrib/deterministic-build/requirements.txt
+ contrib/deterministic-build/requirements-binaries.txt
+ contrib/deterministic-build/requirements-build-base.txt
+ - name: Cache libsecp256k1
+ id: cache-libsecp
+ uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: contrib/_saved_secp256k1_build
+ key: libsecp-${{ runner.os }}-${{ hashFiles('contrib/make_libsecp256k1.sh') }}
+ - name: Build libsecp256k1
+ if: steps.cache-libsecp.outputs.cache-hit != 'true'
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install automake libtool
+ ./contrib/make_libsecp256k1.sh
+ mkdir -p contrib/_saved_secp256k1_build
+ cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/
+ - name: Install Qt/QML runtime deps
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libgl1 libegl1 libxkbcommon0 libdbus-1-3
+ - name: Install Python dependencies (frozen)
+ run: |
+ pip install -r contrib/deterministic-build/requirements-build-base.txt
+ pip install -r contrib/requirements/requirements-ci.txt
+ pip install -r contrib/deterministic-build/requirements.txt -r contrib/deterministic-build/requirements-binaries.txt
+ pip install ".[tests,qml_gui]"
+ - name: Log versions
+ run: python3 --version && pip freeze --all
+ - name: Run pytest
+ run: pytest tests -v
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.