Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 25 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,6 @@ jobs:
- uses: astral-sh/setup-uv@v7
- run: uv build --sdist
- uses: actions/upload-artifact@v7
id: upload
continue-on-error: true
with:
name: sdist
path: dist/*.tar.gz
- uses: actions/upload-artifact@v7
if: steps.upload.outcome == 'failure'
with:
name: sdist
path: dist/*.tar.gz
Expand All @@ -65,13 +58,18 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-15-intel, macos-15, windows-latest]
# PRs: oldest and newest only, with and without the GIL
python: ${{ fromJSON(github.event_name == 'pull_request' && '["cp39","cp315","cp315t"]' || '["cp39","cp310","cp311","cp312","cp313","cp314","cp314t","cp315","cp315t"]') }}
# PRs and main: oldest and newest only, with and without the GIL; all of them for tags, Fridays and by hand
python: ${{ fromJSON((startsWith(github.ref, 'refs/tags/') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '["cp39","cp310","cp311","cp312","cp313","cp314","cp314t","cp315","cp315t"]' || '["cp39","cp315","cp315t"]') }}
# Intel macOS with the full matrix only
exclude:
- os: ${{ !(startsWith(github.ref, 'refs/tags/') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && 'macos-15-intel' || '' }}
env:
WRTC_CACHE_DIR: ${{ github.workspace }}/.wrtc-cache
WRTC_ARCHIVE_DIR: ${{ matrix.os == 'windows-latest' && format('{0}/.wrtc-cache/archives', github.workspace) || '' }}
UV_HTTP_RETRIES: 10
CIBW_ENVIRONMENT_PASS_LINUX: UV_HTTP_RETRIES
# --gc-on-emit not on main and tags
CIBW_TEST_COMMAND: ${{ github.event_name != 'push' && 'pytest {project}/tests && pytest {project}/tests --gc-on-emit' || 'pytest {project}/tests' }}
steps:
- uses: actions/checkout@v7
with:
Expand Down Expand Up @@ -103,30 +101,29 @@ jobs:
with:
package-dir: ${{ env.SDIST }}
- uses: actions/upload-artifact@v7
id: upload
continue-on-error: true
with:
name: wheels-${{ matrix.os }}-${{ matrix.python }}
path: wheelhouse/*.whl
- uses: actions/upload-artifact@v7
if: steps.upload.outcome == 'failure'
with:
name: wheels-${{ matrix.os }}-${{ matrix.python }}
path: wheelhouse/*.whl
overwrite: true

sanitizers:
name: sanitizers (ASan + UBSan, ${{ matrix.arch }})
name: sanitizers (ASan + UBSan, ${{ matrix.arch }}, ${{ matrix.pass }})
if: github.event_name != 'push'
runs-on: ${{ matrix.runner }}
container: quay.io/pypa/manylinux_2_28_${{ matrix.arch }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
arch: [x86_64, aarch64]
pass: [plain, gc-on-emit, stress]
include:
- { arch: x86_64, runner: ubuntu-latest }
# also covers the libc++ built from source for arm64
- { arch: aarch64, runner: ubuntu-24.04-arm }
- { pass: plain, args: "" }
- { pass: gc-on-emit, args: --gc-on-emit }
- { pass: stress, args: --stress -k "stress or chaos" }
steps:
- uses: actions/checkout@v7
# github.workspace is the host's path
Expand All @@ -135,16 +132,19 @@ jobs:
with:
path: .wrtc-cache
key: libwebrtc-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('cmake/libwebrtc.cmake', 'cmake/libcxx/*.sha256', 'cmake/openh264.*') }}
- run: .github/scripts/sanitizers.sh
# the collector running on libwebrtc threads, whenever they emit (see tests/conftest.py)
- run: .github/scripts/sanitizers.sh --gc-on-emit
# long random sequences of calls (tests/chaos.py)
- run: .github/scripts/sanitizers.sh --stress -k "stress or chaos"
- run: .github/scripts/sanitizers.sh ${{ matrix.args }}

sanitizers-macos:
name: sanitizers (ASan + UBSan, macOS)
name: sanitizers (ASan + UBSan, macOS, ${{ matrix.pass }})
if: github.event_name != 'push'
runs-on: macos-15
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- { pass: plain, args: "" }
- { pass: gc-on-emit, args: --gc-on-emit }
env:
WRTC_CACHE_DIR: ${{ github.workspace }}/.wrtc-cache
steps:
Expand All @@ -154,12 +154,11 @@ jobs:
path: .wrtc-cache
key: libwebrtc-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('cmake/libwebrtc.cmake', 'cmake/libcxx/*.sha256', 'cmake/openh264.*') }}
- uses: astral-sh/setup-uv@v7
- run: .github/scripts/sanitizers-macos.sh
- run: .github/scripts/sanitizers-macos.sh --gc-on-emit
- run: .github/scripts/sanitizers-macos.sh ${{ matrix.args }}

publish:
if: startsWith(github.ref, 'refs/tags/v')
needs: [lint, sdist, wheels, sanitizers, sanitizers-macos]
needs: [lint, sdist, wheels]
runs-on: ubuntu-latest
environment: pypi
permissions:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ build-frontend = "build[uv]"
# dev builds stay per file to catch missing includes
config-settings = { "cmake.define.CMAKE_UNITY_BUILD" = "ON" }
test-groups = ["test"]
# again with a collection on every emit (--gc-on-emit in tests/conftest.py)
test-command = "pytest {project}/tests && pytest {project}/tests --gc-on-emit"
# CI adds --gc-on-emit on PRs (.github/workflows/ci.yml)
test-command = "pytest {project}/tests"

[tool.cibuildwheel.linux]
archs = ["native"]
Expand Down
12 changes: 11 additions & 1 deletion tests/test_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from tests.helpers import connect, stats_of_type, wait_for_event, wait_until, wait_until_unmuted
from webrtc.models import rtc_stats

ICE_TYPES = {'candidate-pair', 'local-candidate', 'remote-candidate'}


async def send_audio(
caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection, stream: webrtc.MediaStream
Expand All @@ -34,6 +36,11 @@ async def send_audio(
return remote


def ice_free_ids(report: webrtc.RTCStatsReport) -> set[str]:
"""The ids of the stats in a report, but of its candidate pair and candidates."""
return {stats.id for stats in report.values() if stats.type not in ICE_TYPES}


@pytest.mark.asyncio
async def test_connection_stats(
caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection, audio_stream: webrtc.MediaStream
Expand Down Expand Up @@ -74,7 +81,10 @@ async def has_remote_inbound() -> bool:
assert len(stats_of_type(sender_report, 'outbound-rtp')) > 0
assert len(stats_of_type(sender_report, 'inbound-rtp')) == 0
track_report = await caller.get_stats(audio_stream.get_tracks()[0])
assert set(track_report.keys()) == set(sender_report.keys())
assert ice_free_ids(track_report) == ice_free_ids(sender_report)
# ICE may still switch the selected pair between the two reports
assert len(stats_of_type(track_report, 'candidate-pair')) == 1
assert len(stats_of_type(sender_report, 'candidate-pair')) == 1


@pytest.mark.asyncio
Expand Down
Loading