Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
bcfff3e
TURN URI パーサーと TURN ポート取得機能を追加する
voluntas Oct 19, 2025
1d21b3f
tc egress 帯域制限テストを追加する
voluntas Oct 19, 2025
95967ce
Tailscale GitHub Action のバージョンを v4 に更新する
voluntas Oct 19, 2025
868e94b
テスト依存関係に pytest-timeout と pyroute2 を追加する
voluntas Oct 19, 2025
ccd7a6d
e2e-test-tc ワークフローを新規作成し、TC E2E テストの実行環境を構築する
voluntas Oct 19, 2025
dead5ca
tc egress 帯域制限を tbf で実装し pyroute2 に統一する
voluntas Oct 19, 2025
ed52387
E2E テストの実行時に特定のテストファイルを除外する設定を追加する
voluntas Oct 19, 2025
4d1209d
TC E2E テストの実行時に pytest に -s オプションを追加する
voluntas Oct 19, 2025
00270c6
pyroute2 のインポートを例外処理で囲み、印刷文を修正する
voluntas Oct 19, 2025
da304d1
pytest-asyncio パッケージを uv.lock から削除する
voluntas Oct 19, 2025
13fcb27
pyroute2 をいったんコメントアウトする
voluntas Oct 19, 2025
9cdb68e
pyroute2 は特別扱いにする
voluntas Oct 19, 2025
2ef207e
pyroute2 パッケージを uv.lock に追加し、win-inet-pton パッケージも追加する
voluntas Oct 19, 2025
7eaa161
e2e-test-tc.yml の条件を修正し、依存関係のインストールをテストと tc グループに変更する
voluntas Oct 19, 2025
88282fd
pyroute2 のインポート方法を修正し、テストの説明を更新する
voluntas Oct 19, 2025
4551e94
tc テストに WebRTC 統計情報の確認機能を追加し pytest.importorskip を使用する
voluntas Oct 19, 2025
71859db
テストで WebRTC 統計情報の確認を動画メディアタイプに限定し、エラーメッセージを更新する
voluntas Oct 19, 2025
2923c45
テストの帯域制限維持時間を5秒から10秒に延長し、targetBitrateの確認メッセージを簡略化する
voluntas Oct 19, 2025
b8554ba
テストで WebRTC 統計情報のメディアタイプの確認条件を修正し、kind を使用するように変更する
voluntas Oct 19, 2025
531bd66
WebRTC 統計情報の表示関数を修正し、引数を SoraClient から取得した統計情報のリストに変更する。制限前後の統計情報を確認す…
voluntas Oct 19, 2025
55c7f86
Merge branch 'develop' into feature/e2e-test-pyroute2-tc
voluntas Nov 3, 2025
81f3716
テスト実行条件を修正し、Linux 環境での実行を追加する
voluntas Nov 3, 2025
ff802c7
tc egress 帯域制限を 500kbps から 250kbps に変更し、音声送信を無効にしてテストの待機時間を延長する
voluntas Nov 3, 2025
2cdd5e8
帯域制限のテストでのコメントを更新し、制限値を500kbpsから750kbpsに変更する
voluntas Nov 3, 2025
dcfe9c1
Python のバージョン要件を 3.11 以上に設定し、末尾コメントの利用を禁止する
voluntas Nov 3, 2025
cb9501a
テストの設定値を定数化し、帯域制限のテストメッセージを改善する
voluntas Nov 3, 2025
7d9fa43
WebRTC 統計情報の出力を改善し、simulcast ストリームの targetBitrate を確認するテストを追加する
voluntas Nov 3, 2025
dbbf886
テストでのビデオの解像度を設定し、幅を960px、高さを540pxに変更する
voluntas Nov 3, 2025
3506cea
初期ビットレートを1500kbpsから1200kbpsに変更し、制限前の最小ビットレートを750kbpsから500kbpsに修正する。テス…
voluntas Nov 3, 2025
9f80738
simulcast の帯域制限テストを改善し、targetBitrate の確認を追加する
voluntas Nov 3, 2025
333943f
simulcast の帯域制限テストを改善し、targetBitrate の確認を強化する
voluntas Nov 3, 2025
ec101ce
テストの出力メッセージを英語に変更し、帯域制限の適用状況を明確にする
voluntas Nov 3, 2025
cbc5b5b
Merge branch 'develop' into feature/e2e-test-pyroute2-tc
voluntas Nov 10, 2025
28e2e56
tc 統計情報表示を改善し、tbf または netem qdisc のみを表示するように変更。設定がない場合のメッセージも追加。
voluntas Nov 10, 2025
8bc2210
tc 統計情報の取得方法を TCA_STATS2 属性から取得するように修正する
voluntas Nov 10, 2025
318e8fa
tc テストコードの品質を改善する
voluntas Nov 10, 2025
04ef88d
tc 統計情報取得のフォールバックコードを削除する
voluntas Nov 10, 2025
20585c1
WebRTC 統計情報を rid でソートして表示するように改善する
voluntas Nov 10, 2025
4221b2f
tc テストのエラーハンドリングを改善する
voluntas Nov 10, 2025
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
172 changes: 172 additions & 0 deletions .github/workflows/e2e-test-tc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
name: e2e-test-tc

on:
workflow_dispatch:
workflow_call:
inputs:
from_build:
type: boolean
default: false
description: "build.yml から呼び出されたかどうか"
push:
branches:
- develop
- "feature/**"
paths:
- ".github/workflows/e2e-test-tc.yml"
- "tests/test_tc.py"
schedule:
# UTC の 01:00 は JST だと 10:00 。
# 1-5 で 月曜日から金曜日
- cron: "0 1 * * 1-5"

env:
TEST_SIGNALING_URLS: ${{ secrets.TEST_SIGNALING_URLS }}
TEST_CHANNEL_ID_PREFIX: ${{ secrets.TEST_CHANNEL_ID_PREFIX }}
TEST_SECRET_KEY: ${{ secrets.TEST_SECRET_KEY }}
TEST_API_URL: ${{ secrets.TEST_API_URL }}
OPENH264_VERSION: 2.6.0

permissions:
contents: read
actions: read

jobs:
e2e_test_tc:
# TC テストは Linux でのみ実行(tc は Linux カーネル機能)
# Python 3.14 かつ Ubuntu 24.04 固定
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v5

# Tailscale のセットアップ
- uses: tailscale/github-action@v4
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
tags: tag:ci
use-cache: "true"
hostname: gha-tc-${{ github.run_id }}-${{ github.run_number }}
version: latest
timeout: 2m
retry: 5

# Linux 向けの依存関係インストール
- run: |
sudo apt-get update
sudo apt-get -y install libx11-dev libdrm-dev libva-dev

# UV のセットアップ
- uses: astral-sh/setup-uv@v7
with:
enable-cache: false
# Python バージョンの設定(3.14 固定)
python-version: "3.14"

# 依存関係のインストール (test と tc グループ)
- run: uv sync --no-install-project --group test --group tc

# Wheel ファイルのダウンロード (build.yml から呼び出された場合: 呼び出し元の artifact を使用)
- if: inputs.from_build == true
uses: actions/download-artifact@v5
with:
name: ubuntu-24.04_x86_64_python-3.14
path: dist/

# Wheel ファイルのダウンロード (直接実行の場合: 同一ブランチの最新成功ビルド artifact または GitHub Release から取得)
- if: inputs.from_build != true
uses: ./.github/actions/download-whl
id: download-whl
with:
platform_name: ubuntu-24.04_x86_64
python_version: "3.14"
github_token: ${{ secrets.GITHUB_TOKEN }}

# wheel ファイル名を取得
- name: Find wheel file
id: find-wheel
shell: bash
run: |
WHEEL_FILE=$(find dist -name "*.whl" | head -1)
if [ -z "$WHEEL_FILE" ]; then
echo "No wheel file found in dist/"
exit 1
fi
echo "wheel_file=$WHEEL_FILE" >> $GITHUB_OUTPUT

# wheel ファイルのインストール
- name: Install wheel package
run: |
echo "Installing wheel: ${{ steps.find-wheel.outputs.wheel_file }}"

# wheel ファイルの内容を確認
echo "Checking wheel contents:"
unzip -l "${{ steps.find-wheel.outputs.wheel_file }}" | grep -E "sora_sdk|METADATA" | head -20

# uv pip install を使う理由:
# uv add はワークスペース依存関係の管理用で、プロジェクト名 (sora-sdk) と
# 同じ名前の wheel ファイルを追加しようとすると自己参照エラーになる
# uv pip install は仮想環境に直接インストールするため、この制限を回避できる
uv pip install "${{ steps.find-wheel.outputs.wheel_file }}"

# OpenH264 ライブラリのダウンロード
- uses: shiguredo/github-actions/.github/actions/download-openh264@main
id: openh264
with:
openh264_version: ${{ env.OPENH264_VERSION }}
use-cache: true

# OpenH264 パスを環境変数に設定
- name: Set OpenH264 path
run: echo "OPENH264_PATH=${{ steps.openh264.outputs.openh264_path }}" >> $GITHUB_ENV

# ネットワークインターフェースの確認
- name: Check network interface
run: |
echo "=== Network interfaces ==="
ip link show
echo ""
echo "=== Default route ==="
ip route show default
echo ""
echo "=== Current tc qdisc settings ==="
tc qdisc show || true

# TC E2E テストの実行(root 権限が必要)
- name: Run TC E2E tests
run: |
# UV_NO_SYNC=1 を指定する理由:
# uv run はデフォルトで実行前に pyproject.toml/uv.lock と環境を同期する
# この同期により、uv pip install でインストールした wheel が削除され、
# 元のソースコードの sora-sdk がインストールされてしまう
# UV_NO_SYNC=1 で同期をスキップし、インストール済みの wheel を使用する

# tc の設定には root 権限が必要なため sudo で実行
# -E オプションで環境変数を継承
# $(which uv) で uv のフルパスを指定(sudo 実行時に PATH が変わるため)
sudo -E $(which uv) run pytest tests/test_tc.py -v --durations=0 -s
env:
UV_NO_SYNC: 1
TC: 1

# テスト後の tc 設定のクリーンアップ確認
- name: Verify tc cleanup
if: always()
run: |
echo "=== TC qdisc settings after test ==="
tc qdisc show || true

slack_notify_failed:
needs: [e2e_test_tc]
runs-on: ubuntu-24.04
if: failure() && inputs.from_build != true
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: sora-python-sdk
SLACK_COLOR: danger
SLACK_TITLE: "TC E2E Test FAILED"
SLACK_ICON_EMOJI: ":japanese_ogre:"
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
3 changes: 2 additions & 1 deletion .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
paths:
- ".github/workflows/e2e-test.yml"
- "tests/**"
- "!tests/**/test_tc*"
schedule:
# UTC の 01:00 は JST だと 10:00 。
# 1-5 で 月曜日から金曜日
Expand Down Expand Up @@ -242,7 +243,7 @@ jobs:
# この同期により、uv pip install でインストールした wheel が削除され、
# 元のソースコードの sora-sdk がインストールされてしまう
# UV_NO_SYNC=1 で同期をスキップし、インストール済みの wheel を使用する
uv run pytest ${{ matrix.platform.test_target || 'tests/' }} -v --durations=0 ${{ steps.pytest-args.outputs.pytest_extra_args }}
uv run pytest ${{ matrix.platform.test_target || 'tests/' }} -v --durations=0 ${{ steps.pytest-args.outputs.pytest_extra_args }} --ignore-glob=**/test_tc*.py -s
env:
UV_NO_SYNC: 1

Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@
- コミットメッセージは日本語で書くこと
- コミットメッセージは命令形で書くこと
- コミットメッセージは〜するという形で書くこと

## Python について

- Python のバージョンは 3.11 以上を前提とすること
- 末尾コメントを利用しないこと
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ build-backend = "setuptools.build_meta"

[dependency-groups]
dev = ["nanobind==2.9.2"]
test = ["pytest", "pytest-repeat", "pytest-xdist", "numpy", "httpx", "pyjwt"]
test = [
"pytest",
"pytest-timeout",
"pytest-repeat",
"pytest-xdist",
"numpy",
"httpx",
"pyjwt",
]
tc = ["pyroute2"]
lint = ["ruff", "ty"]

[tool.uv]
Expand Down
63 changes: 63 additions & 0 deletions tests/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,69 @@ def disconnect_message(self) -> Optional[dict[str, Any]]:
def close_message(self) -> Optional[dict[str, Any]]:
return self._close_message

@property
def turn_ports(self) -> dict[str, list[int]]:
"""TURN URI (RFC 7065) からポート情報を抽出する"""
ports_map: dict[str, list[int]] = {"udp": [], "tcp": [], "tls": []}
if self._offer_message is None:
return ports_map

config = self._offer_message.get("config")
if config is None:
return ports_map

ice_servers = config.get("iceServers", [])
for server in ice_servers:
urls = server.get("urls", [])
if isinstance(urls, str):
urls = [urls]
for uri in urls:
if not (uri.startswith("turn:") or uri.startswith("turns:")):
continue

# TURN URI を手動でパースする
# RFC 7065: turn:host:port?transport=udp
# RFC 7065: turns:host:port?transport=tcp
is_turns = uri.startswith("turns:")
scheme = "turns:" if is_turns else "turn:"

# スキームを除去
uri_without_scheme = uri[len(scheme):]

# クエリパラメータを分離
query = ""
if "?" in uri_without_scheme:
uri_without_scheme, query = uri_without_scheme.split("?", 1)

# ホストとポートを分離
if ":" not in uri_without_scheme:
continue

# 最後のコロンでホストとポートを分割
host_port = uri_without_scheme.rsplit(":", 1)
if len(host_port) != 2:
continue

try:
port = int(host_port[1])
except ValueError:
continue

# turns: の場合は tls
if is_turns:
if port not in ports_map["tls"]:
ports_map["tls"].append(port)
# turn: の場合は query の transport を確認
else:
transport = "udp" # デフォルトは udp
if query:
params = dict(param.split("=") for param in query.split("&") if "=" in param)
transport = params.get("transport", "udp")
if transport in ports_map and port not in ports_map[transport]:
ports_map[transport].append(port)

return ports_map

@property
def connected(self) -> bool:
return self._connected.is_set()
Expand Down
Loading