diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml new file mode 100644 index 0000000..9239011 --- /dev/null +++ b/.github/workflows/code.yml @@ -0,0 +1,157 @@ +name: Unit Tests & Code Coverage + +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: # allow manual triggering + +defaults: + run: + shell: bash -l {0} + +jobs: + lint: + name: Code style + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Upgrade pip & test with pre-commit + run: | + set -vxeuo pipefail + python -m pip install --upgrade pip + python -m pip install pre-commit + pre-commit run --all-files + + test-matrix: + name: Python ${{ matrix.python-version }} # - Redis ${{ matrix.redis-version }} + runs-on: ubuntu-latest + needs: lint + strategy: + matrix: + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + # - "3.13" waiting for upstream packages + # redis-version: + # # - "6" + # - "7" + max-parallel: 5 + env: + DISPLAY: ":99.0" + + steps: + - uses: actions/checkout@v4 + + - name: Install OS libraries to test Linux PyQt apps + run: | + sudo apt update -y + sudo apt install -y \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-randr0 \ + libxcb-render-util0 \ + libxcb-xinerama0 \ + libxcb-xfixes0 \ + libxkbcommon-x11-0 \ + screen \ + x11-utils \ + xvfb + + # FIXME: conflicts if redis is running on host (local runners) + # - name: Start Redis + # uses: supercharge/redis-github-action@1.7.0 + # with: + # redis-version: ${{ matrix.redis-version }} + # redis-remove-container: true # false by default + + - name: Create environment Python ${{ matrix.python-version }} # - Redis ${{ matrix.redis-version }} + # needed for Unpack step + uses: mamba-org/setup-micromamba@v1 + with: + cache-environment: true + cache-environment-key: env-key-${{ matrix.python-version }} + condarc: | + channel-priority: flexible + # environment-file: environment.yml + environment-name: anaconda-test-env-py-${{ matrix.python-version }} # -${{ matrix.redis-version }} + create-args: >- + coveralls + pytest + pytest-cov + pytest-qt + pytest-xvfb + python=${{ matrix.python-version }} + setuptools-scm + pyqt>5.15 + pyepics + + - name: Install this package with pip + shell: bash -l {0} + run: | + set -vxeuo pipefail + pip install -e . + + - name: Run tests with pytest & coverage + shell: bash -l {0} + run: | + set -vxeuo pipefail + coverage run --concurrency=thread --parallel-mode -m pytest -vvv --exitfirst . + coverage combine + coverage report --precision 3 + + - name: Upload coverage data to coveralls.io + shell: bash -l {0} + run: | + set -vxeuo pipefail + micromamba list coveralls + which coveralls + coveralls debug + if [ "${GITHUB_TOKEN}" != "" ]; then + # only upload from GitHub runner + coveralls --service=github + else + echo "No credentials for upload to coveralls." + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: ${{ matrix.python-version }} # -${{ matrix.redis-version }} + COVERALLS_PARALLEL: true + + # https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html#github-actions-support + coveralls: + name: Report unit test coverage to coveralls + needs: test-matrix + runs-on: ubuntu-latest + container: python:3-slim + + steps: + - name: Gather coverage and report to Coveralls + run: | + set -vxeuo pipefail + echo "Finally!" + pip3 install --upgrade coveralls + # debug mode: output prepared json and reported files list to stdout + # https://coveralls-python.readthedocs.io/en/latest/troubleshooting.html + coveralls debug + if [ "${GITHUB_TOKEN}" != "" ]; then + # only upload from GitHub runner + coveralls --service=github --finish + else + echo "No credentials for upload to coveralls." + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 0dca057..4198342 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ maintainers = [ { name = "Rafael Vescovi", email = "rvescovi@anl.gov" }, ] readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.9" keywords = ['bluesky', 'queueserver'] license = { file = "src/instrument/LICENSE" } classifiers = [ @@ -127,7 +127,8 @@ addopts = ["--import-mode=importlib", "-x"] junit_family = "xunit1" filterwarnings = [ "ignore::DeprecationWarning", - "ignore::pottery.exceptions.InefficientAccessWarning", + "ignore::PendingDeprecationWarning", + # "ignore::pottery.exceptions.InefficientAccessWarning", ] [tool.ruff] diff --git a/src/instrument/tests/test_general.py b/src/instrument/tests/test_general.py new file mode 100644 index 0000000..2de9e5b --- /dev/null +++ b/src/instrument/tests/test_general.py @@ -0,0 +1,72 @@ +""" +Test that instrument can be started. + +Here is just enough test to get a CI workflow started. More are possible. +""" + +import pytest + +from ..startup import RE +from ..startup import bec +from ..startup import cat +from ..startup import iconfig +from ..startup import peaks +from ..startup import running_in_queueserver +from ..startup import sd +from ..startup import sim_count_plan +from ..startup import sim_print_plan +from ..startup import sim_rel_scan_plan +from ..startup import specwriter + + +def test_startup(): + """Test that standard startup works.""" + assert cat is not None + assert bec is not None + assert peaks is not None + assert sd is not None + assert iconfig is not None + assert RE is not None + assert specwriter is not None + assert len(cat) == 0 + assert not running_in_queueserver() + + +@pytest.mark.parametrize( + "plan, n_uids", + [ + [sim_print_plan, 0], + [sim_count_plan, 1], + [sim_rel_scan_plan, 1], + ], +) +def test_sim_plans(plan, n_uids): + """Test supplied simulator plans.""" + bec.disable_plots() + n_runs = len(cat) + uids = RE(plan()) + assert len(uids) == n_uids + assert len(cat) == n_runs + len(uids) + + +def test_iconfig(): + """Test the instrument configuration.""" + version = iconfig.get("ICONFIG_VERSION", "0.0.0") + assert version >= "2.0.0" + + cat_name = iconfig.get("DATABROKER_CATALOG") + assert cat_name is not None + assert cat_name == cat.name + + assert "RUN_ENGINE" in iconfig + assert "DEFAULT_METADATA" in iconfig["RUN_ENGINE"] + + default_md = iconfig["RUN_ENGINE"]["DEFAULT_METADATA"] + assert "beamline_id" in default_md + assert "instrument_name" in default_md + assert "proposal_id" in default_md + assert "databroker_catalog" in default_md + assert default_md["databroker_catalog"] == cat.name + + xmode = iconfig.get("XMODE_DEBUG_LEVEL") + assert xmode is not None