Skip to content

Commit 191b453

Browse files
committed
chore: Add shared helper functions used across the livekit-rtc test suite.
1 parent 3116a21 commit 191b453

7 files changed

Lines changed: 127 additions & 192 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,17 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
15+
"""Put this tests directory on sys.path so test modules can `from utils import ...`.
16+
17+
Both `livekit-rtc/tests/` and the repo-root `tests/` are collected by pytest
18+
together; if either has an `__init__.py` they collide on the `tests` namespace
19+
package name. So we keep this directory non-package and use absolute imports.
20+
"""
21+
22+
import sys
23+
from pathlib import Path
24+
25+
_TESTS_DIR = Path(__file__).resolve().parent
26+
if str(_TESTS_DIR) not in sys.path:
27+
sys.path.insert(0, str(_TESTS_DIR))

livekit-rtc/tests/test_audio.py

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,23 @@
1414

1515
"""End-to-end audio publish/subscribe tests."""
1616

17+
from __future__ import annotations
18+
1719
import asyncio
1820
import ctypes
1921
import math
2022
import os
21-
import uuid
2223
import wave
2324
from pathlib import Path
2425

2526
import numpy as np
2627
import pytest
2728

28-
from livekit import api, rtc
29+
from livekit import rtc
2930
from livekit.rtc.audio_frame import AudioFrame
3031

32+
from utils import create_token, skip_if_no_credentials, unique_room_name
33+
3134

3235
SAMPLE_RATE = 48000
3336
NUM_CHANNELS = 1
@@ -36,33 +39,6 @@
3639
AMPLITUDE = 0.5
3740

3841

39-
def skip_if_no_credentials():
40-
required_vars = ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"]
41-
missing = [var for var in required_vars if not os.getenv(var)]
42-
return pytest.mark.skipif(
43-
bool(missing), reason=f"Missing environment variables: {', '.join(missing)}"
44-
)
45-
46-
47-
def create_token(identity: str, room_name: str) -> str:
48-
return (
49-
api.AccessToken()
50-
.with_identity(identity)
51-
.with_name(identity)
52-
.with_grants(
53-
api.VideoGrants(
54-
room_join=True,
55-
room=room_name,
56-
)
57-
)
58-
.to_jwt()
59-
)
60-
61-
62-
def unique_room_name(base: str) -> str:
63-
return f"{base}-{uuid.uuid4().hex[:8]}"
64-
65-
6642
def _generate_sine_wave(
6743
frequency: int,
6844
sample_rate: int,
@@ -132,7 +108,7 @@ def _band_energies(
132108
class TestAudioStreamPublishSubscribe:
133109
"""End-to-end: publish a sine sweep into a room and verify spectrum on the subscriber."""
134110

135-
async def test_audio_stream_publish_subscribe(self):
111+
async def test_audio_stream_publish_subscribe(self) -> None:
136112
"""Publish 5 seconds of 100/300/500/700/1000 Hz tones and FFT-verify received audio."""
137113
url = os.environ["LIVEKIT_URL"]
138114
room_name = unique_room_name("test-audio-sweep")
@@ -151,7 +127,7 @@ def on_track_subscribed(
151127
track: rtc.Track,
152128
publication: rtc.RemoteTrackPublication,
153129
participant: rtc.RemoteParticipant,
154-
):
130+
) -> None:
155131
nonlocal subscribed_track
156132
if track.kind == rtc.TrackKind.KIND_AUDIO:
157133
subscribed_track = track

livekit-rtc/tests/test_change_video_quality.py

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,17 @@
4242
import os
4343
import sys
4444
import time
45-
import uuid
46-
from typing import Callable, Optional, Tuple
45+
from typing import Any, Callable, Optional, Tuple
4746

4847
import numpy as np
4948
import pytest
5049

51-
from livekit import api, rtc
50+
from livekit import rtc
5251
from livekit.rtc._proto.track_publication_pb2 import VideoQuality
5352
from livekit.rtc.room import EventTypes
5453

54+
from utils import create_token, skip_if_no_credentials, unique_room_name
55+
5556

5657
WAIT_TIMEOUT = 30.0
5758
WAIT_INTERVAL = 0.1
@@ -73,33 +74,6 @@
7374
]
7475

7576

76-
def skip_if_no_credentials():
77-
required_vars = ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"]
78-
missing = [var for var in required_vars if not os.getenv(var)]
79-
return pytest.mark.skipif(
80-
bool(missing), reason=f"Missing environment variables: {', '.join(missing)}"
81-
)
82-
83-
84-
def create_token(identity: str, room_name: str) -> str:
85-
return (
86-
api.AccessToken()
87-
.with_identity(identity)
88-
.with_name(identity)
89-
.with_grants(
90-
api.VideoGrants(
91-
room_join=True,
92-
room=room_name,
93-
)
94-
)
95-
.to_jwt()
96-
)
97-
98-
99-
def unique_room_name(base: str) -> str:
100-
return f"{base}-{uuid.uuid4().hex[:8]}"
101-
102-
10377
async def _wait_until(
10478
predicate: Callable[[], bool],
10579
*,
@@ -149,7 +123,7 @@ def _expect_event(
149123
loop = asyncio.get_running_loop()
150124
fut: asyncio.Future = loop.create_future()
151125

152-
def _on_event(*args, **kwargs) -> None:
126+
def _on_event(*args: Any, **kwargs: Any) -> None:
153127
if fut.done():
154128
return
155129
if predicate is None or predicate(*args, **kwargs):

livekit-rtc/tests/test_e2ee_per_participant.py

Lines changed: 9 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@
1818

1919
import asyncio
2020
import os
21-
import uuid
22-
from typing import Any, Callable, TypeVar
2321

2422
import pytest
2523

26-
from livekit import api, rtc
24+
from livekit import rtc
25+
26+
from utils import (
27+
assert_eventually,
28+
create_token,
29+
skip_if_no_credentials,
30+
unique_room_name,
31+
)
2732

2833

2934
# Per-participant keys (publisher identity → list of (key_bytes, key_index))
@@ -37,51 +42,6 @@
3742
WIDTH, HEIGHT = 320, 180
3843
FRAME_RATE = 15
3944

40-
T = TypeVar("T")
41-
42-
43-
def skip_if_no_credentials() -> Any:
44-
required_vars = ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"]
45-
missing = [var for var in required_vars if not os.getenv(var)]
46-
return pytest.mark.skipif(
47-
bool(missing), reason=f"Missing environment variables: {', '.join(missing)}"
48-
)
49-
50-
51-
def create_token(identity: str, room_name: str) -> str:
52-
return (
53-
api.AccessToken()
54-
.with_identity(identity)
55-
.with_name(identity)
56-
.with_grants(
57-
api.VideoGrants(
58-
room_join=True,
59-
room=room_name,
60-
)
61-
)
62-
.to_jwt()
63-
)
64-
65-
66-
def unique_room_name(base: str) -> str:
67-
return f"{base}-{uuid.uuid4().hex[:8]}"
68-
69-
70-
async def assert_eventually(
71-
condition: Callable[[], T],
72-
timeout: float = 15.0,
73-
interval: float = 0.1,
74-
message: str = "Condition not met within timeout",
75-
) -> T:
76-
deadline = asyncio.get_event_loop().time() + timeout
77-
last_result = None
78-
while asyncio.get_event_loop().time() < deadline:
79-
last_result = condition()
80-
if last_result:
81-
return last_result
82-
await asyncio.sleep(interval)
83-
raise AssertionError(f"{message} (last result: {last_result})")
84-
8545

8646
def make_per_participant_e2ee_options() -> rtc.E2EEOptions:
8747
options = rtc.E2EEOptions()
@@ -126,7 +86,7 @@ async def publish_dummy_video(source: rtc.VideoSource, stop_event: asyncio.Event
12686

12787

12888
@pytest.mark.asyncio
129-
@skip_if_no_credentials() # type: ignore[untyped-decorator]
89+
@skip_if_no_credentials()
13090
async def test_e2ee_per_participant() -> None:
13191
"""E2E test for per-participant E2EE keys.
13292

livekit-rtc/tests/test_e2ee_shared_key.py

Lines changed: 9 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,64 +18,24 @@
1818

1919
import asyncio
2020
import os
21-
import uuid
22-
from typing import Any, Callable, TypeVar
2321

2422
import pytest
2523

26-
from livekit import api, rtc
24+
from livekit import rtc
25+
26+
from utils import (
27+
assert_eventually,
28+
create_token,
29+
skip_if_no_credentials,
30+
unique_room_name,
31+
)
2732

2833

2934
SHARED_KEY = b"12345678"
3035
WRONG_KEY = b"wrongkey"
3136
WIDTH, HEIGHT = 320, 180
3237
FRAME_RATE = 15
3338

34-
T = TypeVar("T")
35-
36-
37-
def skip_if_no_credentials() -> Any:
38-
required_vars = ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"]
39-
missing = [var for var in required_vars if not os.getenv(var)]
40-
return pytest.mark.skipif(
41-
bool(missing), reason=f"Missing environment variables: {', '.join(missing)}"
42-
)
43-
44-
45-
def create_token(identity: str, room_name: str) -> str:
46-
return (
47-
api.AccessToken()
48-
.with_identity(identity)
49-
.with_name(identity)
50-
.with_grants(
51-
api.VideoGrants(
52-
room_join=True,
53-
room=room_name,
54-
)
55-
)
56-
.to_jwt()
57-
)
58-
59-
60-
def unique_room_name(base: str) -> str:
61-
return f"{base}-{uuid.uuid4().hex[:8]}"
62-
63-
64-
async def assert_eventually(
65-
condition: Callable[[], T],
66-
timeout: float = 10.0,
67-
interval: float = 0.1,
68-
message: str = "Condition not met within timeout",
69-
) -> T:
70-
deadline = asyncio.get_event_loop().time() + timeout
71-
last_result = None
72-
while asyncio.get_event_loop().time() < deadline:
73-
last_result = condition()
74-
if last_result:
75-
return last_result
76-
await asyncio.sleep(interval)
77-
raise AssertionError(f"{message} (last result: {last_result})")
78-
7939

8040
def make_e2ee_options() -> rtc.E2EEOptions:
8141
options = rtc.E2EEOptions()
@@ -105,7 +65,7 @@ async def publish_dummy_video(source: rtc.VideoSource, stop_event: asyncio.Event
10565

10666

10767
@pytest.mark.asyncio
108-
@skip_if_no_credentials() # type: ignore[untyped-decorator]
68+
@skip_if_no_credentials()
10969
async def test_e2ee_shared_key() -> None:
11070
"""E2E test for shared-key E2EE.
11171

livekit-rtc/tests/test_video.py

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@
1414

1515
"""End-to-end video publish/subscribe tests."""
1616

17+
from __future__ import annotations
18+
1719
import asyncio
1820
import os
1921
import struct
20-
import uuid
2122
import zlib
2223
from pathlib import Path
2324

2425
import numpy as np
25-
import pytest
2626

27-
from livekit import api, rtc
27+
from livekit import rtc
28+
29+
from utils import create_token, skip_if_no_credentials, unique_room_name
2830

2931

3032
VIDEO_WIDTH = 640
@@ -41,33 +43,6 @@
4143
]
4244

4345

44-
def skip_if_no_credentials():
45-
required_vars = ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"]
46-
missing = [var for var in required_vars if not os.getenv(var)]
47-
return pytest.mark.skipif(
48-
bool(missing), reason=f"Missing environment variables: {', '.join(missing)}"
49-
)
50-
51-
52-
def create_token(identity: str, room_name: str) -> str:
53-
return (
54-
api.AccessToken()
55-
.with_identity(identity)
56-
.with_name(identity)
57-
.with_grants(
58-
api.VideoGrants(
59-
room_join=True,
60-
room=room_name,
61-
)
62-
)
63-
.to_jwt()
64-
)
65-
66-
67-
def unique_room_name(base: str) -> str:
68-
return f"{base}-{uuid.uuid4().hex[:8]}"
69-
70-
7146
def _solid_color_rgba_frame(width: int, height: int, rgb: tuple[int, int, int]) -> rtc.VideoFrame:
7247
"""Build a solid-color 640x480 RGBA `VideoFrame` for the given RGB triple."""
7348
pixels = np.empty((height, width, 4), dtype=np.uint8)
@@ -129,7 +104,7 @@ def _chunk(tag: bytes, data: bytes) -> bytes:
129104
class TestVideoStreamPublishSubscribe:
130105
"""End-to-end: publish a 640x480 color-cycle video and verify colors on the subscriber."""
131106

132-
async def test_video_stream_publish_subscribe(self):
107+
async def test_video_stream_publish_subscribe(self) -> None:
133108
"""Publish red/green/blue/white/black (1s each, 15fps) and verify color sequence."""
134109
url = os.environ["LIVEKIT_URL"]
135110
room_name = unique_room_name("test-video-colors")
@@ -148,7 +123,7 @@ def on_track_subscribed(
148123
track: rtc.Track,
149124
publication: rtc.RemoteTrackPublication,
150125
participant: rtc.RemoteParticipant,
151-
):
126+
) -> None:
152127
nonlocal subscribed_track
153128
if track.kind == rtc.TrackKind.KIND_VIDEO:
154129
subscribed_track = track

0 commit comments

Comments
 (0)