From 9bcd1879d3c2f3522bfd4814b164dfa4e3cdfb56 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Tue, 7 Apr 2026 11:51:47 -0400 Subject: [PATCH 1/7] Replace cancel-workflow-action with concurrency --- .github/workflows/pytest.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 6c97c3b5f..d6b4e3902 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -12,6 +12,11 @@ on: - '.github/dependabot.yml' workflow_call: +# Cancel workflow runs if a new commit is pushed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: name: pytest with coverage @@ -19,11 +24,6 @@ jobs: timeout-minutes: 60 steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.13.1 - with: - access_token: ${{ github.token }} - - name: Install system packages run: | sudo apt-get install -y socat From f484758d140d2bfcf60c38d3734feb55f5ff67fc Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Tue, 7 Apr 2026 12:12:29 -0400 Subject: [PATCH 2/7] Refactor pytest Actions workflow These changes were all based on those made in [1]. [1] - https://github.com/simonsobs/ocs/pull/448 --- .github/workflows/pytest.yml | 101 +++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index d6b4e3902..47ce2ef85 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -18,27 +18,21 @@ concurrency: cancel-in-progress: true jobs: - test: - name: pytest with coverage - runs-on: ubuntu-latest + unit-test: + name: Run unit tests + runs-on: ubuntu-22.04 # 3.7 needs 22.04, can move to -latest when we drop 3.7 support timeout-minutes: 60 + strategy: + matrix: + python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - name: Install system packages - run: | - sudo apt-get install -y socat - - - name: Set up Python 3.8 + - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v6 with: - python-version: 3.8 + python-version: ${{ matrix.python }} - # Install - - name: Install so3g - run: | - pip3 install so3g - - - name: clone socs + - name: Clone socs uses: actions/checkout@v6 - name: Install socs @@ -46,25 +40,88 @@ jobs: pip3 install -r requirements.txt pip3 install -e . - # Unit Tests - name: Run unit tests working-directory: ./tests run: | - COVERAGE_FILE=.coverage.unit python3 -m pytest --cov -m 'not integtest' + COVERAGE_FILE=.coverage.unit-${{ matrix.python }} python3 -m pytest --cov -m 'not integtest' + + - name: Upload coverage file artifact + uses: actions/upload-artifact@v7 + with: + name: coverage-${{ matrix.python }} + include-hidden-files: true + path: ./tests/.coverage.* + + integ-test: + name: Run integration tests + runs-on: ubuntu-latest + + steps: + - name: Install system packages + run: | + sudo apt-get install -y socat + + # Run on a single version, since these tests are run in Docker + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: 3.12 + + - name: Clone socs + uses: actions/checkout@v6 + + - name: Install socs + run: | + pip3 install -r requirements.txt + pip3 install -e . - # Integration Tests - name: Run integration tests working-directory: ./tests run: | COVERAGE_FILE=.coverage.int python3 -m pytest --cov -m 'integtest' - # Coverage - - name: Report test coverage + - name: Upload coverage file artifact + uses: actions/upload-artifact@v7 + with: + name: coverage-integ + include-hidden-files: true + path: ./tests/.coverage.* + + upload-coverage: + name: Upload coverage + needs: [unit-test, integ-test] + runs-on: ubuntu-latest + + steps: + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: 3.12 + + - name: Install coverage + run: | + python -m pip install coverage + + # Coverage report doesn't work unless we have a checkout + - name: Clone socs + uses: actions/checkout@v6 + + # Download after checkout, else artifacts get deleted + - name: Download coverage artifacts + uses: actions/download-artifact@v8 + + - name: List coverage output for upload + run: | + ls -lthra + ls -lthra coverage* + pwd + + - name: Report and upload test coverage env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mv ./tests/.coverage.* ./ - pip install -U coveralls + mv ./coverage-*/.coverage.* ./ coverage combine coverage report + pip install -U coveralls coveralls --service=github From 49cdf439b87db8509aac360d9cb21fdb74e93103 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Tue, 7 Apr 2026 12:15:56 -0400 Subject: [PATCH 3/7] Drop testing for 3.12 This is broken for the way we install in the workflow, but should still work if sotodlib is pip installed separately. --- .github/workflows/pytest.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 47ce2ef85..de94cd803 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -24,7 +24,9 @@ jobs: timeout-minutes: 60 strategy: matrix: - python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + # Don't test in 3.12 until #1035 is fixed + # https://github.com/simonsobs/socs/issues/1035 + python: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - name: Set up Python ${{ matrix.python }} @@ -62,10 +64,10 @@ jobs: sudo apt-get install -y socat # Run on a single version, since these tests are run in Docker - - name: Set up Python 3.12 + - name: Set up Python 3.11 uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.11 - name: Clone socs uses: actions/checkout@v6 @@ -93,10 +95,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Python 3.12 + - name: Set up Python 3.11 uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.11 - name: Install coverage run: | From 6e826f5518b25ef723dc85e3d0cf4fea7b9ff94d Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Tue, 7 Apr 2026 12:20:13 -0400 Subject: [PATCH 4/7] Drop support for Python 3.7 --- .github/workflows/pytest.yml | 4 ++-- pyproject.toml | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index de94cd803..383ac04c0 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -20,13 +20,13 @@ concurrency: jobs: unit-test: name: Run unit tests - runs-on: ubuntu-22.04 # 3.7 needs 22.04, can move to -latest when we drop 3.7 support + runs-on: ubuntu-latest timeout-minutes: 60 strategy: matrix: # Don't test in 3.12 until #1035 is fixed # https://github.com/simonsobs/socs/issues/1035 - python: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python: ["3.8", "3.9", "3.10", "3.11"] steps: - name: Set up Python ${{ matrix.python }} diff --git a/pyproject.toml b/pyproject.toml index 377cb3bbf..13091cf3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,12 @@ name = "socs" dynamic = ["version"] description = "Simons Observatory Control System" readme = "README.rst" -requires-python = ">=3.7" +requires-python = ">=3.8" classifiers = [ "Framework :: Twisted", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", From 97658c494f5696ada7d15faaf064c6fae95c8635 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Tue, 7 Apr 2026 13:31:58 -0400 Subject: [PATCH 5/7] Set appropriate timeouts --- .github/workflows/pytest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 383ac04c0..371e0f7ea 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -21,7 +21,7 @@ jobs: unit-test: name: Run unit tests runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 10 strategy: matrix: # Don't test in 3.12 until #1035 is fixed @@ -57,6 +57,7 @@ jobs: integ-test: name: Run integration tests runs-on: ubuntu-latest + timeout-minutes: 60 steps: - name: Install system packages From 9529fd4714eb1f24209512be832f82185eead575 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Tue, 7 Apr 2026 14:30:54 -0400 Subject: [PATCH 6/7] Group integ tests to run in parallel jobs --- .github/workflows/pytest.yml | 15 ++++++++++++--- .../test_cryomech_cpa_agent_integration.py | 5 +++++ .../integration/test_hwp_pid_agent_integration.py | 13 +++++++------ .../integration/test_hwp_pmx_agent_integration.py | 15 +++++++++------ .../test_hwp_supervisor_agent_integration.py | 2 ++ .../test_ibootbar_agent_integration.py | 7 +++++++ tests/integration/test_ls240_agent_integration.py | 4 ++++ tests/integration/test_ls425_agent_integration.py | 12 ++++++------ .../test_pfeiffer_tc400_agent_integration.py | 9 +++++++++ .../test_pfeiffer_tpg366_agent_integration.py | 1 + .../test_pysmurf_monitor_integration.py | 3 +++ .../test_scpi_psu_agent_integration.py | 6 ++++++ tests/integration/test_synacc_integration.py | 5 +++++ .../test_ucsc_radiometer_agent_integration.py | 1 + tests/integration/test_ups_agent_integration.py | 1 + tests/pytest.ini | 6 ++++++ 16 files changed, 84 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 371e0f7ea..4756814f3 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -58,6 +58,15 @@ jobs: name: Run integration tests runs-on: ubuntu-latest timeout-minutes: 60 + strategy: + matrix: + marker: + - 'http' + - 'hwp' + - 'pysmurf' + - 'serial' + - 'snmp' + - 'tcp' steps: - name: Install system packages @@ -78,15 +87,15 @@ jobs: pip3 install -r requirements.txt pip3 install -e . - - name: Run integration tests + - name: Run ${{ matrix.marker }} integration tests working-directory: ./tests run: | - COVERAGE_FILE=.coverage.int python3 -m pytest --cov -m 'integtest' + COVERAGE_FILE=.coverage.int.${{ matrix.marker }} python3 -m pytest --cov -m 'integtest and ${{ matrix.marker }}' - name: Upload coverage file artifact uses: actions/upload-artifact@v7 with: - name: coverage-integ + name: coverage-integ-${{ matrix.marker }} include-hidden-files: true path: ./tests/.coverage.* diff --git a/tests/integration/test_cryomech_cpa_agent_integration.py b/tests/integration/test_cryomech_cpa_agent_integration.py index ea4f32322..0b400a1ce 100644 --- a/tests/integration/test_cryomech_cpa_agent_integration.py +++ b/tests/integration/test_cryomech_cpa_agent_integration.py @@ -25,6 +25,7 @@ emulator = create_device_emulator({init_msg: init_res}, relay_type='tcp', port=5502, encoding=None) +@pytest.mark.tcp @pytest.mark.integtest def test_cryomech_cpa_init(wait_for_crossbar, emulator, run_agent, client): @@ -36,6 +37,7 @@ def test_cryomech_cpa_init(wait_for_crossbar, emulator, run_agent, assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest @pytest.mark.parametrize("state,command", [('on', b'\t\x99\x00\x00\x00\x06\x01\x06\x00\x01\x00\x01'), ('off', b'\t\x99\x00\x00\x00\x06\x01\x06\x00\x01\x00\xff')]) @@ -54,6 +56,7 @@ def test_cryomech_cpa_power_ptc(wait_for_crossbar, emulator, run_agent, assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest def test_cryomech_cpa_auto_start_acq(wait_for_crossbar, emulator, run_agent_acq, client): client.init() @@ -65,6 +68,7 @@ def test_cryomech_cpa_auto_start_acq(wait_for_crossbar, emulator, run_agent_acq, assert resp.status == ocs.OK +@pytest.mark.tcp @pytest.mark.integtest def test_cryomech_cpa_acq(wait_for_crossbar, emulator, run_agent, client): client.init() @@ -82,6 +86,7 @@ def test_cryomech_cpa_acq(wait_for_crossbar, emulator, run_agent, client): print(resp) +@pytest.mark.tcp @pytest.mark.integtest @pytest.mark.parametrize("state,command", [('on', b'\t\x99\x00\x00\x00\x06\x01\x06\x00\x01\x00\x01')]) def test_cryomech_cpa_release_reacquire(wait_for_crossbar, emulator, run_agent_acq, diff --git a/tests/integration/test_hwp_pid_agent_integration.py b/tests/integration/test_hwp_pid_agent_integration.py index 79100b478..8beddb5c6 100644 --- a/tests/integration/test_hwp_pid_agent_integration.py +++ b/tests/integration/test_hwp_pid_agent_integration.py @@ -67,12 +67,7 @@ def wait_for_main(client): time.sleep(0.2) -@pytest.mark.integtest -def test_testing(wait_for_crossbar): - """Just a quick test to make sure we can bring up crossbar.""" - assert True - - +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_get_state(wait_for_crossbar, pid_agent, client): resp = client.get_state() @@ -81,6 +76,7 @@ def test_hwp_rotation_get_state(wait_for_crossbar, pid_agent, client): assert len(state.keys()) > 0 +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_direction(wait_for_crossbar, pid_agent, client): wait_for_main(client) @@ -97,6 +93,7 @@ def test_hwp_rotation_set_direction(wait_for_crossbar, pid_agent, client): assert data['direction'] == 1 +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_pid(wait_for_crossbar, pid_agent, client): wait_for_main(client) @@ -107,6 +104,7 @@ def test_hwp_rotation_set_pid(wait_for_crossbar, pid_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_tune_stop(wait_for_crossbar, pid_agent, client): wait_for_main(client) @@ -117,6 +115,7 @@ def test_hwp_rotation_tune_stop(wait_for_crossbar, pid_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_scale(wait_for_crossbar, pid_agent, client): wait_for_main(client) @@ -127,6 +126,7 @@ def test_hwp_rotation_set_scale(wait_for_crossbar, pid_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_declare_freq(wait_for_crossbar, pid_agent, client): wait_for_main(client) @@ -137,6 +137,7 @@ def test_hwp_rotation_declare_freq(wait_for_crossbar, pid_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_tune_freq(wait_for_crossbar, pid_agent, client): wait_for_main(client) diff --git a/tests/integration/test_hwp_pmx_agent_integration.py b/tests/integration/test_hwp_pmx_agent_integration.py index 5fbc88af9..55febf2e5 100644 --- a/tests/integration/test_hwp_pmx_agent_integration.py +++ b/tests/integration/test_hwp_pmx_agent_integration.py @@ -23,12 +23,7 @@ kikusui_emu = create_device_emulator(responses, relay_type='tcp', port=5025) -@pytest.mark.integtest -def test_testing(wait_for_crossbar): - """Just a quick test to make sure we can bring up crossbar.""" - assert True - - +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_main(wait_for_crossbar, kikusui_emu, run_agent, client): client.main.stop() @@ -38,6 +33,7 @@ def test_hwp_rotation_main(wait_for_crossbar, kikusui_emu, run_agent, client): assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_off(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses({ @@ -50,6 +46,7 @@ def test_hwp_rotation_set_off(wait_for_crossbar, kikusui_emu, run_agent, client) assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_on(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses({ @@ -62,6 +59,7 @@ def test_hwp_rotation_set_on(wait_for_crossbar, kikusui_emu, run_agent, client): assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_i(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses( @@ -74,6 +72,7 @@ def test_hwp_rotation_set_i(wait_for_crossbar, kikusui_emu, run_agent, client): assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_i_lim(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses( @@ -85,6 +84,7 @@ def test_hwp_rotation_set_i_lim(wait_for_crossbar, kikusui_emu, run_agent, clien assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_v(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses( @@ -96,6 +96,7 @@ def test_hwp_rotation_set_v(wait_for_crossbar, kikusui_emu, run_agent, client): assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_set_v_lim(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses({ @@ -109,6 +110,7 @@ def test_hwp_rotation_set_v_lim(wait_for_crossbar, kikusui_emu, run_agent, clien assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_use_ext(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses({ @@ -122,6 +124,7 @@ def test_hwp_rotation_use_ext(wait_for_crossbar, kikusui_emu, run_agent, client) assert resp.session['success'] +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_rotation_ign_ext(wait_for_crossbar, kikusui_emu, run_agent, client): kikusui_emu.update_responses({ diff --git a/tests/integration/test_hwp_supervisor_agent_integration.py b/tests/integration/test_hwp_supervisor_agent_integration.py index 20c1b4a29..bb7669486 100644 --- a/tests/integration/test_hwp_supervisor_agent_integration.py +++ b/tests/integration/test_hwp_supervisor_agent_integration.py @@ -166,6 +166,7 @@ def get_hwp_state(client) -> Dict[str, Any]: return client.query_hwp_state().session["data"]["state"] +@pytest.mark.hwp @pytest.mark.integtest def test_supervisor_grip(hwp_em, supervisor_agent, sup_client) -> None: state = get_hwp_state(sup_client) @@ -183,6 +184,7 @@ def test_supervisor_grip(hwp_em, supervisor_agent, sup_client) -> None: assert False +@pytest.mark.hwp @pytest.mark.integtest def test_hwp_spinup(supervisor_agent, sup_client) -> None: assert not get_hwp_state(sup_client)["is_spinning"] diff --git a/tests/integration/test_ibootbar_agent_integration.py b/tests/integration/test_ibootbar_agent_integration.py index 17d044f24..d00c91a89 100644 --- a/tests/integration/test_ibootbar_agent_integration.py +++ b/tests/integration/test_ibootbar_agent_integration.py @@ -56,6 +56,7 @@ def start_responder(): os.kill(p.pid, signal.SIGINT) +@pytest.mark.snmp @pytest.mark.integtest def test_ibootbar_acq(wait_for_crossbar, start_responder, run_agent, client): resp = client.acq.start(test_mode=True) @@ -63,6 +64,7 @@ def test_ibootbar_acq(wait_for_crossbar, start_responder, run_agent, client): check_resp_success(resp) +@pytest.mark.snmp @pytest.mark.integtest @inlineCallbacks def test_ibootbar_set_outlet(wait_for_crossbar, start_responder, run_agent, client): @@ -81,6 +83,7 @@ def test_ibootbar_set_outlet(wait_for_crossbar, start_responder, run_agent, clie assert resp.session["data"][f"outletStatus_{outlet_number - 1}"]["status"] == 1 +@pytest.mark.snmp @pytest.mark.integtest def test_ibootbar_set_initial_state( wait_for_crossbar, start_responder, run_agent, client): @@ -88,12 +91,14 @@ def test_ibootbar_set_initial_state( check_resp_success(resp) +@pytest.mark.snmp @pytest.mark.integtest def test_ibootbar_cycle_outlet(wait_for_crossbar, start_responder, run_agent, client): resp = client.cycle_outlet(outlet=7, cycle_time=5) check_resp_success(resp) +@pytest.mark.snmp @pytest.mark.integtest def test_ibootbar_set_locked_outlet(wait_for_crossbar, start_responder, run_agent, client): resp = client.lock_outlet(outlet=2, lock=True) @@ -103,6 +108,7 @@ def test_ibootbar_set_locked_outlet(wait_for_crossbar, start_responder, run_agen assert resp.session["op_code"] == OpCode.FAILED.value +@pytest.mark.snmp @pytest.mark.integtest def test_ibootbar_cycle_locked_outlet(wait_for_crossbar, start_responder, run_agent, client): resp = client.lock_outlet(outlet=2, lock=True) @@ -112,6 +118,7 @@ def test_ibootbar_cycle_locked_outlet(wait_for_crossbar, start_responder, run_ag assert resp.session["op_code"] == OpCode.FAILED.value +@pytest.mark.snmp @pytest.mark.integtest def test_ibootbar_unlock_outlet(wait_for_crossbar, start_responder, run_agent, client): resp = client.set_outlet(outlet=1, state="off") diff --git a/tests/integration/test_ls240_agent_integration.py b/tests/integration/test_ls240_agent_integration.py index ac8c27066..7a1145866 100644 --- a/tests/integration/test_ls240_agent_integration.py +++ b/tests/integration/test_ls240_agent_integration.py @@ -35,6 +35,7 @@ emulator = create_device_emulator(initial_responses, relay_type='serial') +@pytest.mark.serial @pytest.mark.integtest def test_ls240_init_lakeshore(wait_for_crossbar, emulator, run_agent, client): resp = client.init_lakeshore() @@ -44,6 +45,7 @@ def test_ls240_init_lakeshore(wait_for_crossbar, emulator, run_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.serial @pytest.mark.integtest def test_ls240_start_acq(wait_for_crossbar, emulator, run_agent, client): client.init_lakeshore() @@ -88,6 +90,7 @@ def test_ls240_start_acq(wait_for_crossbar, emulator, run_agent, client): OpCode.SUCCEEDED.value] +@pytest.mark.serial @pytest.mark.integtest def test_ls240_set_values(wait_for_crossbar, emulator, run_agent, client): client.init_lakeshore() @@ -108,6 +111,7 @@ def test_ls240_set_values(wait_for_crossbar, emulator, run_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.serial @pytest.mark.integtest def test_ls240_upload_cal_curve(wait_for_crossbar, emulator, run_agent, client, tmp_path): diff --git a/tests/integration/test_ls425_agent_integration.py b/tests/integration/test_ls425_agent_integration.py index 9662551d5..d63ca47fa 100644 --- a/tests/integration/test_ls425_agent_integration.py +++ b/tests/integration/test_ls425_agent_integration.py @@ -19,12 +19,7 @@ relay_type='serial') -@pytest.mark.integtest -def test_testing(wait_for_crossbar): - """Just a quick test to make sure we can bring up crossbar.""" - assert True - - +@pytest.mark.serial @pytest.mark.integtest def test_ls425_init_lakeshore(wait_for_crossbar, emulator, run_agent, client): # init_lakeshore runs at startup @@ -35,6 +30,7 @@ def test_ls425_init_lakeshore(wait_for_crossbar, emulator, run_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.serial @pytest.mark.integtest def test_ls425_auto_start_acq(wait_for_crossbar, emulator, run_agent_acq, client): # init_lakeshore runs at startup @@ -53,6 +49,7 @@ def test_ls425_auto_start_acq(wait_for_crossbar, emulator, run_agent_acq, client assert resp.session['op_code'] == OpCode.RUNNING.value +@pytest.mark.serial @pytest.mark.integtest def test_ls425_start_acq(wait_for_crossbar, emulator, run_agent, client): responses = {'*IDN?': 'LSCI,MODEL425,LSA425T,1.3', @@ -79,6 +76,7 @@ def test_ls425_start_acq(wait_for_crossbar, emulator, run_agent, client): assert resp.session['op_code'] in [OpCode.STOPPING.value, OpCode.SUCCEEDED.value] +@pytest.mark.serial @pytest.mark.integtest def test_ls425_operational_status(wait_for_crossbar, emulator, run_agent, client): responses = {'OPST?': '132'} @@ -89,6 +87,7 @@ def test_ls425_operational_status(wait_for_crossbar, emulator, run_agent, client assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.serial @pytest.mark.integtest def test_ls425_zero_calibration(wait_for_crossbar, emulator, run_agent, client): responses = {'ZCLEAR': '', @@ -100,6 +99,7 @@ def test_ls425_zero_calibration(wait_for_crossbar, emulator, run_agent, client): assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.serial @pytest.mark.integtest def test_ls425_any_command(wait_for_crossbar, emulator, run_agent, client): responses = {'UNIT 2': '', diff --git a/tests/integration/test_pfeiffer_tc400_agent_integration.py b/tests/integration/test_pfeiffer_tc400_agent_integration.py index c5c359fe3..86a37d4c9 100644 --- a/tests/integration/test_pfeiffer_tc400_agent_integration.py +++ b/tests/integration/test_pfeiffer_tc400_agent_integration.py @@ -46,6 +46,7 @@ def format_reply(data): emulator = create_device_emulator({}, relay_type='tcp') +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_init_lakeshore(wait_for_crossbar, emulator, run_agent, client): @@ -56,6 +57,7 @@ def test_pfeiffer_tc400_init_lakeshore(wait_for_crossbar, emulator, run_agent, assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_turn_turbo_on(wait_for_crossbar, emulator, run_agent, client): @@ -73,6 +75,7 @@ def test_pfeiffer_tc400_turn_turbo_on(wait_for_crossbar, emulator, run_agent, assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_turn_turbo_on_not_ready(wait_for_crossbar, emulator, run_agent, client): @@ -88,6 +91,7 @@ def test_pfeiffer_tc400_turn_turbo_on_not_ready(wait_for_crossbar, emulator, assert resp.session['op_code'] == OpCode.FAILED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_turn_turbo_on_failed(wait_for_crossbar, emulator, run_agent, client): @@ -103,6 +107,7 @@ def test_pfeiffer_tc400_turn_turbo_on_failed(wait_for_crossbar, emulator, assert resp.session['op_code'] == OpCode.FAILED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_turn_turbo_off(wait_for_crossbar, emulator, run_agent, client): @@ -118,6 +123,7 @@ def test_pfeiffer_tc400_turn_turbo_off(wait_for_crossbar, emulator, run_agent, assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_turn_turbo_off_failed(wait_for_crossbar, emulator, run_agent, client): @@ -133,6 +139,7 @@ def test_pfeiffer_tc400_turn_turbo_off_failed(wait_for_crossbar, emulator, assert resp.session['op_code'] == OpCode.FAILED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_turn_turbo_off_failed_unready(wait_for_crossbar, emulator, run_agent, @@ -149,6 +156,7 @@ def test_pfeiffer_tc400_turn_turbo_off_failed_unready(wait_for_crossbar, assert resp.session['op_code'] == OpCode.FAILED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_acknowledge_turbo_errors(wait_for_crossbar, emulator, run_agent, client): @@ -163,6 +171,7 @@ def test_pfeiffer_tc400_acknowledge_turbo_errors(wait_for_crossbar, emulator, assert resp.session['op_code'] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tc400_acq(wait_for_crossbar, emulator, run_agent, client): client.init() diff --git a/tests/integration/test_pfeiffer_tpg366_agent_integration.py b/tests/integration/test_pfeiffer_tpg366_agent_integration.py index 9d40ebe94..03e53b7da 100644 --- a/tests/integration/test_pfeiffer_tpg366_agent_integration.py +++ b/tests/integration/test_pfeiffer_tpg366_agent_integration.py @@ -33,6 +33,7 @@ def check_resp_success(resp): assert resp.session["op_code"] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest def test_pfeiffer_tpg366_acq(wait_for_crossbar, emu, run_agent, client): resp = client.acq.start(test_mode=True) diff --git a/tests/integration/test_pysmurf_monitor_integration.py b/tests/integration/test_pysmurf_monitor_integration.py index f08b98c82..eae56658a 100644 --- a/tests/integration/test_pysmurf_monitor_integration.py +++ b/tests/integration/test_pysmurf_monitor_integration.py @@ -83,6 +83,7 @@ def write_test_file(path): return full_path +@pytest.mark.pysmurf @pytest.mark.integtest def test_pysmurf_monitor_run_data_file(wait_for_crossbar, publisher, run_agent, client, cleanup, tmp_path): @@ -103,6 +104,7 @@ def test_pysmurf_monitor_run_data_file(wait_for_crossbar, publisher, run_agent, check_resp(client.run.status()) +@pytest.mark.pysmurf @pytest.mark.integtest def test_pysmurf_monitor_run_session_log(wait_for_crossbar, publisher, run_agent, client, cleanup): log_message = "This is a test log message" @@ -112,6 +114,7 @@ def test_pysmurf_monitor_run_session_log(wait_for_crossbar, publisher, run_agent check_resp(client.run.status()) +@pytest.mark.pysmurf @pytest.mark.integtest def test_pysmurf_monitor_run_metadata(wait_for_crossbar, publisher, run_agent, client, cleanup, tmp_path): diff --git a/tests/integration/test_scpi_psu_agent_integration.py b/tests/integration/test_scpi_psu_agent_integration.py index 8b77d7103..f531a0dd7 100644 --- a/tests/integration/test_scpi_psu_agent_integration.py +++ b/tests/integration/test_scpi_psu_agent_integration.py @@ -39,12 +39,14 @@ def check_resp_success(resp): assert resp.session["op_code"] == OpCode.SUCCEEDED.value +@pytest.mark.tcp @pytest.mark.integtest def test_scpi_psu_init_psu(wait_for_crossbar, gpib_emu, run_agent, client): resp = client.init() check_resp_success(resp) +@pytest.mark.tcp @pytest.mark.integtest def test_scpi_psu_init_psu_acq_mode(wait_for_crossbar, gpib_emu, run_agent_acq, client): # Sleep to give time for the agent to initialize and enter the acq process @@ -53,6 +55,7 @@ def test_scpi_psu_init_psu_acq_mode(wait_for_crossbar, gpib_emu, run_agent_acq, check_resp_success(resp) +@pytest.mark.tcp @pytest.mark.integtest def test_scpi_psu_set_output(wait_for_crossbar, gpib_emu, run_agent, client): client.init() @@ -63,6 +66,7 @@ def test_scpi_psu_set_output(wait_for_crossbar, gpib_emu, run_agent, client): check_resp_success(resp) +@pytest.mark.tcp @pytest.mark.integtest def test_scpi_psu_set_current(wait_for_crossbar, gpib_emu, run_agent, client): client.init() @@ -70,6 +74,7 @@ def test_scpi_psu_set_current(wait_for_crossbar, gpib_emu, run_agent, client): check_resp_success(resp) +@pytest.mark.tcp @pytest.mark.integtest def test_scpi_psu_set_voltage(wait_for_crossbar, gpib_emu, run_agent, client): client.init() @@ -77,6 +82,7 @@ def test_scpi_psu_set_voltage(wait_for_crossbar, gpib_emu, run_agent, client): check_resp_success(resp) +@pytest.mark.tcp @pytest.mark.integtest def test_scpi_psu_monitor_output(wait_for_crossbar, gpib_emu, run_agent, client): responses = { diff --git a/tests/integration/test_synacc_integration.py b/tests/integration/test_synacc_integration.py index d02b76316..0e56eb97a 100644 --- a/tests/integration/test_synacc_integration.py +++ b/tests/integration/test_synacc_integration.py @@ -52,6 +52,7 @@ def check_resp_state(resp, opcode=OpCode.SUCCEEDED.value): assert resp.session["op_code"] == opcode +@pytest.mark.http @pytest.mark.integtest def test_synacc_startup(wait_for_crossbar, http_mock, run_agent, client): with http_mock: @@ -61,6 +62,7 @@ def test_synacc_startup(wait_for_crossbar, http_mock, run_agent, client): check_resp_state(resp, OpCode.RUNNING.value) +@pytest.mark.http @pytest.mark.integtest def test_synacc_reboot(wait_for_crossbar, http_mock, run_agent, client): with http_mock: @@ -68,6 +70,7 @@ def test_synacc_reboot(wait_for_crossbar, http_mock, run_agent, client): check_resp_state(resp) +@pytest.mark.http @pytest.mark.integtest def test_synacc_set_outlet_on(wait_for_crossbar, http_mock, run_agent, client): with http_mock: @@ -75,6 +78,7 @@ def test_synacc_set_outlet_on(wait_for_crossbar, http_mock, run_agent, client): check_resp_state(resp) +@pytest.mark.http @pytest.mark.integtest def test_synacc_set_outlet_off(wait_for_crossbar, http_mock, run_agent, client): with http_mock: @@ -82,6 +86,7 @@ def test_synacc_set_outlet_off(wait_for_crossbar, http_mock, run_agent, client): check_resp_state(resp) +@pytest.mark.http @pytest.mark.integtest def test_synacc_set_all(wait_for_crossbar, http_mock, run_agent, client): with http_mock: diff --git a/tests/integration/test_ucsc_radiometer_agent_integration.py b/tests/integration/test_ucsc_radiometer_agent_integration.py index a6b70cb23..ea1c27157 100644 --- a/tests/integration/test_ucsc_radiometer_agent_integration.py +++ b/tests/integration/test_ucsc_radiometer_agent_integration.py @@ -36,6 +36,7 @@ def route_fn(): return app.run("127.0.0.1", 5000) +@pytest.mark.http @pytest.mark.integtest def test_ucsc_radiometer_acq(wait_for_crossbar, http_mock, run_agent, client): with http_mock: diff --git a/tests/integration/test_ups_agent_integration.py b/tests/integration/test_ups_agent_integration.py index 9ab381e0d..be3822f28 100644 --- a/tests/integration/test_ups_agent_integration.py +++ b/tests/integration/test_ups_agent_integration.py @@ -53,6 +53,7 @@ def start_responder(): os.kill(p.pid, signal.SIGINT) +@pytest.mark.snmp @pytest.mark.integtest def test_ups_acq(wait_for_crossbar, start_responder, run_agent, client): resp = client.acq.start(test_mode=True) diff --git a/tests/pytest.ini b/tests/pytest.ini index 691adbfc6..d631277ee 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,5 +1,11 @@ [pytest] norecursedirs = hardware markers = + http: marks tests for agents that use HTTP (deselect with '-m "not http"') + hwp: marks tests for hwp agents (deselect with '-m "not hwp"') integtest: marks tests as integration test (deselect with '-m "not integtest"') + pysmurf: marks tests for pysmurf agents (deselect with '-m "not pysmurf"') + serial: marks tests for agents that use serial (deselect with '-m "not serial"') + snmp: marks tests for agents that use SNMP (deselect with '-m "not snmp"') spt3g: marks tests that depend on spt3g (deselect with '-m "not spt3g"') + tcp: marks tests for agents that use TCP (deselect with '-m "not tcp"') From 25990229514908eccf15ca364e77d2d95638d230 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Tue, 7 Apr 2026 14:48:07 -0400 Subject: [PATCH 7/7] Updated skipped-pytest workflow --- .github/workflows/skipped-pytest.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/skipped-pytest.yml b/.github/workflows/skipped-pytest.yml index ffe495760..59030d4dc 100644 --- a/.github/workflows/skipped-pytest.yml +++ b/.github/workflows/skipped-pytest.yml @@ -12,8 +12,15 @@ on: - '.github/dependabot.yml' jobs: - test: - name: pytest with coverage + unit-test: + name: Run unit tests (3.11) + runs-on: ubuntu-latest + + steps: + - run: 'echo "No build required" ' + + integ-test: + name: Run integration tests runs-on: ubuntu-latest steps: