Skip to content

Commit

Permalink
Merge branch 'develop' into feature/e2e-test-simulcast
Browse files Browse the repository at this point in the history
  • Loading branch information
voluntas committed Jan 8, 2025
2 parents 6fb68f9 + 9b164e2 commit 8a38527
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 22 deletions.
22 changes: 13 additions & 9 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,7 @@

### misc

- [ADD] macos-15 を E2E テストに追加する
- @voluntas
- [ADD] canary.py を追加
- @voluntas
- [ADD] Python 3.13 を E2E テストに追加する
- @voluntas
- [ADD] macos-15 を E2E テストに追加する
- [UPDATE] Boost のダウンロード URL を変更する
- @voluntas
- [UPDATE] ubuntu-latest を ubuntu-24.04 に変更する
- @voluntas
Expand All @@ -90,15 +84,25 @@
- @voluntas
- [CHANGE] サンプルアプリの E2E テストを一旦削除する
- @voluntas
- [ADD] pyjwt を dev-dependencies に追加する
- @voluntas
- [ADD] macos-15 を E2E テストに追加する
- @voluntas
- [ADD] canary.py を追加
- @voluntas
- [ADD] Python 3.13 を E2E テストに追加する
- @voluntas
- [ADD] macos-15 を E2E テストに追加する
- @voluntas
- [ADD] tests/ に E2E テストを追加する
- @voluntas
- [ADD] examples に E2E テストを追加する
- @voluntas
- [FIX] run.py で local_sora_cpp_sdk_dir を設定した際に boost が引けなくなってしまっている問題を修正する
- @tnoho
- [FIX] examples の設定に virtual = true を指定するようにする
- これを指定しないとエラーになる
- @voluntas
- [ADD] examples に E2E テストを追加する
- @voluntas
- [FIX] サイマルキャストの E2E テストについて encoderImplementation の値チェック内容を緩和する
- サイマルキャストの encoderImplementation のチェックを文字列一致としていたが、帯域推定機能を有効にした後、値が安定しなくなったためチェック内容を緩和した
- サイマルキャストの encoderImplementation の結果を以下の通り修正
Expand Down
2 changes: 1 addition & 1 deletion buildbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ def build_and_install_boost(
):
version_underscore = version.replace(".", "_")
archive = download(
f"https://boostorg.jfrog.io/artifactory/main/release/{version}/source/boost_{version_underscore}.tar.gz",
f"https://archives.boost.io/release/{version}/source/boost_{version_underscore}.tar.gz",
source_dir,
)
extract(archive, output_dir=build_dir, output_dirname="boost")
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ dev-dependencies = [
"wheel~=0.45.1",
"typing-extensions",
"python-dotenv",
"numpy>=2.2.1",
"numpy",
"httpx",
"pytest",
"ruff",
"mypy",
"pyjwt",
]

[tool.ruff]
Expand Down
125 changes: 125 additions & 0 deletions tests/test_authz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import sys
import time
import uuid

import jwt
import pytest
from client import SoraClient, SoraRole


@pytest.mark.skipif(reason="Sora C++ SDK 側の対応が必要")
def test_sendonly_authz_video_true(setup):
"""
- type: connect で audio: true / video: false で繫ぐ
- 認証成功時の払い出しで audio: false / video: true を払い出す
"""
signaling_urls = setup.get("signaling_urls")
channel_id_prefix = setup.get("channel_id_prefix")
secret = setup.get("secret")

channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"

access_token = jwt.encode(
{
"channel_id": channel_id,
"audio": False,
"video": True,
# 現在時刻 + 300 秒 (5分)
"exp": int(time.time()) + 300,
},
secret,
algorithm="HS256",
)

sendonly = SoraClient(
signaling_urls,
SoraRole.SENDONLY,
channel_id,
audio=True,
video=False,
metadata={"access_token": access_token},
)
sendonly.connect(fake_video=False, fake_audio=True)

time.sleep(5)

assert sendonly.offer_message is not None
assert sendonly.offer_message["sdp"] is not None
assert "VP9" in sendonly.offer_message["sdp"]

sendonly_stats = sendonly.get_stats()

sendonly.disconnect()

# codec が無かったら StopIteration 例外が上がる
# 統計で video が見つからないので謎挙動になってる
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
assert sendonly_codec_stats["mimeType"] == "video/VP9"

# outbound-rtp が無かったら StopIteration 例外が上がる
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
assert outbound_rtp_stats["encoderImplementation"] == "libvpx"
assert outbound_rtp_stats["bytesSent"] > 0
assert outbound_rtp_stats["packetsSent"] > 0


@pytest.mark.parametrize(
"video_codec_params",
[
# video_codec, encoder_implementation, decoder_implementation
("VP8", "libvpx"),
("VP9", "libvpx"),
("AV1", "libaom"),
],
)
def test_sendonly_authz_video_codec_type(setup, video_codec_params):
video_codec_type, encoder_implementation = video_codec_params

signaling_urls = setup.get("signaling_urls")
channel_id_prefix = setup.get("channel_id_prefix")
secret = setup.get("secret")

channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"

access_token = jwt.encode(
{
"channel_id": channel_id,
"video": True,
"video_codec_type": video_codec_type,
# 現在時刻 + 300 秒 (5分)
"exp": int(time.time()) + 300,
},
secret,
algorithm="HS256",
)

sendonly = SoraClient(
signaling_urls,
SoraRole.SENDONLY,
channel_id,
audio=False,
video=True,
metadata={"access_token": access_token},
)
sendonly.connect(fake_video=True)

time.sleep(5)

assert sendonly.offer_message is not None
assert sendonly.offer_message["sdp"] is not None
assert video_codec_type in sendonly.offer_message["sdp"]

sendonly_stats = sendonly.get_stats()

sendonly.disconnect()

# codec が無かったら StopIteration 例外が上がる
# 統計で video が見つからないので謎挙動になってる
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"

# outbound-rtp が無かったら StopIteration 例外が上がる
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation
assert outbound_rtp_stats["bytesSent"] > 0
assert outbound_rtp_stats["packetsSent"] > 0
11 changes: 4 additions & 7 deletions tests/test_sendonly_recvonly.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,15 @@ def test_sendonly_recvonly_audio(setup):
assert inbound_rtp_stats["packetsReceived"] > 0


@pytest.fixture(
params=[
@pytest.mark.parametrize(
"video_codec_params",
[
# video_codec, encoder_implementation, decoder_implementation
("VP8", "libvpx", "libvpx"),
("VP9", "libvpx", "libvpx"),
("AV1", "libaom", "dav1d"),
]
],
)
def video_codec_params(request):
return request.param


def test_sendonly_recvonly_video(setup, video_codec_params):
video_codec, encoder_implementation, decoder_implementation = video_codec_params

Expand Down
117 changes: 114 additions & 3 deletions tests/test_sora_disconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time
import uuid

import jwt
import pytest
from api import disconnect_connection_api
from client import SoraClient, SoraRole
Expand Down Expand Up @@ -45,7 +46,42 @@ def test_websocket_signaling_only_disconnect_api(setup):
assert conn.disconnect_reason is not None
assert "DISCONNECTED-API" in conn.disconnect_reason

# TODO: LIFETIME-EXPIRED のテスト

def test_websocket_signaling_only_lifetime_expired(setup):
signaling_urls = setup.get("signaling_urls")
channel_id_prefix = setup.get("channel_id_prefix")
secret = setup.get("secret")

channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"

access_token = jwt.encode(
{
"channel_id": channel_id,
"audio": False,
"video": True,
"connection_lifetime": 3,
# 現在時刻 + 300 秒 (5分)
"exp": int(time.time()) + 300,
},
secret,
algorithm="HS256",
)

with SoraClient(
signaling_urls,
SoraRole.RECVONLY,
channel_id,
audio=True,
video=True,
metadata={"access_token": access_token},
data_channel_signaling=False,
ignore_disconnect_websocket=False,
) as conn:
time.sleep(5)

assert conn.ws_close is True
assert conn.ws_close_code == 1000
assert conn.ws_close_reason == "LIFETIME-EXPIRED"


@pytest.mark.skipif(sys.platform != "linux", reason="linux でのみ実行する")
Expand Down Expand Up @@ -84,7 +120,42 @@ def test_websocket_datachannel_signaling_disconnect_api(setup):
assert conn.disconnect_reason is not None
assert "DISCONNECTED-API" in conn.disconnect_reason

# TODO: LIFETIME-EXPIRED のテスト

def test_websocket_datachannel_signaling_lifetime_expired(setup):
signaling_urls = setup.get("signaling_urls")
channel_id_prefix = setup.get("channel_id_prefix")
secret = setup.get("secret")

channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"

access_token = jwt.encode(
{
"channel_id": channel_id,
"audio": True,
"video": True,
"connection_lifetime": 3,
# 現在時刻 + 300 秒 (5分)
"exp": int(time.time()) + 300,
},
secret,
algorithm="HS256",
)

with SoraClient(
signaling_urls,
SoraRole.RECVONLY,
channel_id,
audio=True,
video=True,
metadata={"access_token": access_token},
data_channel_signaling=True,
ignore_disconnect_websocket=False,
) as conn:
time.sleep(5)

assert conn.ws_close is True
assert conn.ws_close_code == 1000
assert conn.ws_close_reason == "LIFETIME-EXPIRED"


@pytest.mark.skipif(sys.platform != "linux", reason="linux でのみ実行する")
Expand Down Expand Up @@ -130,4 +201,44 @@ def test_datachannel_only_signaling_disconnect_api(setup):
assert conn.disconnect_reason is not None
assert "DISCONNECTED-API" in conn.disconnect_reason

# TODO: LIFETIME-EXPIRED のテスト

def test_datachannel_only_signaling_lifetime_expired(setup):
signaling_urls = setup.get("signaling_urls")
channel_id_prefix = setup.get("channel_id_prefix")
secret = setup.get("secret")

channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"

access_token = jwt.encode(
{
"channel_id": channel_id,
"audio": True,
"video": True,
"connection_lifetime": 3,
# 現在時刻 + 300 秒 (5分)
"exp": int(time.time()) + 300,
},
secret,
algorithm="HS256",
)

with SoraClient(
signaling_urls,
SoraRole.RECVONLY,
channel_id,
audio=True,
video=True,
metadata={"access_token": access_token},
data_channel_signaling=True,
ignore_disconnect_websocket=True,
) as conn:
time.sleep(5)

assert conn.close_message is not None
assert conn.close_message["type"] == "close"
assert conn.close_message["code"] == 1000
assert conn.close_message["reason"] == "LIFETIME-EXPIRED"

assert conn.disconnect_code == SoraSignalingErrorCode.CLOSE_SUCCEEDED
assert conn.disconnect_reason is not None
assert "LIFETIME-EXPIRED" in conn.disconnect_reason
13 changes: 12 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8a38527

Please sign in to comment.