Skip to content

Commit d4f9c1c

Browse files
committed
Add unit tests (info, tracks and basic integration test)
1 parent f6a602a commit d4f9c1c

File tree

3 files changed

+296
-0
lines changed

3 files changed

+296
-0
lines changed

tests/test_info.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python3
2+
3+
import unittest
4+
5+
from teleoprtc.info import parse_info_from_offer
6+
7+
8+
def lf2crlf(x):
9+
return x.replace("\n", "\r\n")
10+
11+
12+
class TestStream(unittest.TestCase):
13+
def test_double_video_tracks(self):
14+
sdp = """v=0
15+
o=- 3910210993 3910210993 IN IP4 0.0.0.0
16+
s=-
17+
t=0 0
18+
a=group:BUNDLE 0 1
19+
a=msid-semantic:WMS *
20+
m=video 9 UDP/TLS/RTP/SAVPF 97 98 99 100 101 102
21+
c=IN IP4 0.0.0.0
22+
a=recvonly
23+
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
24+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
25+
a=mid:0
26+
a=msid:e123f852-010c-4b7b-8761-71b72fbfd013 2b75cb0e-6b34-48d6-8bf9-21b809f2e08e
27+
a=rtcp:9 IN IP4 0.0.0.0
28+
a=rtcp-mux
29+
a=ssrc-group:FID 1048118556 4149054509
30+
a=ssrc:1048118556 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
31+
a=ssrc:4149054509 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
32+
a=rtpmap:97 VP8/90000
33+
a=rtcp-fb:97 nack
34+
a=rtcp-fb:97 nack pli
35+
a=rtcp-fb:97 goog-remb
36+
a=rtpmap:98 rtx/90000
37+
a=fmtp:98 apt=97
38+
a=rtpmap:99 H264/90000
39+
a=rtcp-fb:99 nack
40+
a=rtcp-fb:99 nack pli
41+
a=rtcp-fb:99 goog-remb
42+
a=fmtp:99 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
43+
a=rtpmap:100 rtx/90000
44+
a=fmtp:100 apt=99
45+
a=rtpmap:101 H264/90000
46+
a=rtcp-fb:101 nack
47+
a=rtcp-fb:101 nack pli
48+
a=rtcp-fb:101 goog-remb
49+
a=fmtp:101 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
50+
a=rtpmap:102 rtx/90000
51+
a=fmtp:102 apt=101
52+
a=ice-ufrag:jxQW
53+
a=ice-pwd:KpJ0tfaY2RxnIYpTHqPSSv
54+
a=fingerprint:sha-256 70:3A:2D:37:3C:52:96:0E:10:F6:4D:7A:EB:18:38:1B:FD:CA:A5:90:D7:6C:DA:A9:39:76:C9:2F:FB:FF:56:0C
55+
a=setup:actpass
56+
m=video 9 UDP/TLS/RTP/SAVPF 97 98 99 100 101 102
57+
c=IN IP4 0.0.0.0
58+
a=recvonly
59+
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
60+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
61+
a=mid:1
62+
a=msid:e123f852-010c-4b7b-8761-71b72fbfd013 311db759-8d51-479c-a5b4-5c8d055c43ec
63+
a=rtcp:9 IN IP4 0.0.0.0
64+
a=rtcp-mux
65+
a=ssrc-group:FID 4096183284 2713379498
66+
a=ssrc:4096183284 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
67+
a=ssrc:2713379498 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
68+
a=rtpmap:97 VP8/90000
69+
a=rtcp-fb:97 nack
70+
a=rtcp-fb:97 nack pli
71+
a=rtcp-fb:97 goog-remb
72+
a=rtpmap:98 rtx/90000
73+
a=fmtp:98 apt=97
74+
a=rtpmap:99 H264/90000
75+
a=rtcp-fb:99 nack
76+
a=rtcp-fb:99 nack pli
77+
a=rtcp-fb:99 goog-remb
78+
a=fmtp:99 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
79+
a=rtpmap:100 rtx/90000
80+
a=fmtp:100 apt=99
81+
a=rtpmap:101 H264/90000
82+
a=rtcp-fb:101 nack
83+
a=rtcp-fb:101 nack pli
84+
a=rtcp-fb:101 goog-remb
85+
a=fmtp:101 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
86+
a=rtpmap:102 rtx/90000
87+
a=fmtp:102 apt=101
88+
a=ice-ufrag:L7gR
89+
a=ice-pwd:U2QG4cQreTFF8SuMtDwczY
90+
a=fingerprint:sha-256 70:3A:2D:37:3C:52:96:0E:10:F6:4D:7A:EB:18:38:1B:FD:CA:A5:90:D7:6C:DA:A9:39:76:C9:2F:FB:FF:56:0C
91+
a=setup:actpass"""
92+
info = parse_info_from_offer(lf2crlf(sdp))
93+
self.assertEqual(info.n_expected_camera_tracks, 2)
94+
self.assertFalse(info.expected_audio_track)
95+
self.assertFalse(info.incoming_audio_track)
96+
self.assertFalse(info.incoming_datachannel)
97+
98+
def test_recvonly_audio(self):
99+
sdp = """v=0
100+
o=- 3910210904 3910210904 IN IP4 0.0.0.0
101+
s=-
102+
t=0 0
103+
a=group:BUNDLE 0
104+
a=msid-semantic:WMS *
105+
m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8
106+
c=IN IP4 0.0.0.0
107+
a=recvonly
108+
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
109+
a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level
110+
a=mid:0
111+
a=msid:eb1d3f1a-569a-465f-b419-319477bfded6 e44eecb2-1a04-4547-97d8-481389f50d5b
112+
a=rtcp:9 IN IP4 0.0.0.0
113+
a=rtcp-mux
114+
a=ssrc:1233332626 cname:ca4dede8-4994-4a6d-9ae3-923b28177ca5
115+
a=rtpmap:96 opus/48000/2
116+
a=rtpmap:0 PCMU/8000
117+
a=rtpmap:8 PCMA/8000
118+
a=ice-ufrag:kzUw
119+
a=ice-pwd:zltaGj4AKjLq17qay3XUA5
120+
a=fingerprint:sha-256 40:4B:14:CF:70:B8:67:E1:B1:FF:7E:F9:22:6E:60:7D:73:B5:1E:38:4B:10:20:9C:CD:1C:47:02:52:ED:45:25
121+
a=setup:actpass"""
122+
info = parse_info_from_offer(lf2crlf(sdp))
123+
self.assertEqual(info.n_expected_camera_tracks, 0)
124+
self.assertTrue(info.expected_audio_track)
125+
self.assertFalse(info.incoming_audio_track)
126+
self.assertFalse(info.incoming_datachannel)
127+
128+
def test_incoming_datachanel(self):
129+
sdp = """v=0
130+
o=- 3910211092 3910211092 IN IP4 0.0.0.0
131+
s=-
132+
t=0 0
133+
a=group:BUNDLE 0
134+
a=msid-semantic:WMS *
135+
m=application 9 DTLS/SCTP 5000
136+
c=IN IP4 0.0.0.0
137+
a=mid:0
138+
a=sctpmap:5000 webrtc-datachannel 65535
139+
a=max-message-size:65536
140+
a=ice-ufrag:gJlk
141+
a=ice-pwd:UEbnBKS8SihwKnPcx0hiY1
142+
a=fingerprint:sha-256 9B:C0:F3:35:8E:05:A1:15:DB:F8:39:0E:B0:E0:0C:EB:82:E4:B9:26:18:A6:43:2D:B9:9A:23:96:0A:59:B6:58
143+
a=setup:actpass"""
144+
info = parse_info_from_offer(lf2crlf(sdp))
145+
self.assertEqual(info.n_expected_camera_tracks, 0)
146+
self.assertFalse(info.expected_audio_track)
147+
self.assertFalse(info.incoming_audio_track)
148+
self.assertTrue(info.incoming_datachannel)
149+
150+
151+
if __name__ == '__main__':
152+
unittest.main()

tests/test_integration.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python3
2+
3+
import asyncio
4+
import unittest
5+
6+
import aiortc
7+
from aiortc.mediastreams import AudioStreamTrack, VideoStreamTrack
8+
from parameterized import parameterized
9+
10+
from teleoprtc.builder import WebRTCOfferBuilder, WebRTCAnswerBuilder
11+
from teleoprtc.stream import StreamingOffer
12+
from teleoprtc.info import parse_info_from_offer
13+
14+
15+
class SimpleAnswerProvider:
16+
def __init__(self):
17+
self.stream = None
18+
19+
async def __call__(self, offer: StreamingOffer):
20+
assert self.stream is None, "This may only be called once"
21+
22+
info = parse_info_from_offer(offer.sdp)
23+
24+
builder = WebRTCAnswerBuilder(offer.sdp)
25+
for cam in offer.video:
26+
builder.add_video_stream(cam, VideoStreamTrack())
27+
if info.expected_audio_track:
28+
builder.add_audio_stream(AudioStreamTrack())
29+
if info.incoming_audio_track:
30+
builder.offer_to_receive_audio_stream()
31+
32+
self.stream = builder.stream()
33+
answer = await self.stream.start()
34+
35+
return answer
36+
37+
class TestStreamIntegration(unittest.IsolatedAsyncioTestCase):
38+
@parameterized.expand([
39+
# name, recv_cameras, recv_audio, messaging
40+
("multi_camera", ["driver", "wideRoad", "road"], False, False),
41+
("camera_and_audio", ["driver"], True, False),
42+
("camera_and__messaging", ["driver"], False, True),
43+
("camera_and_audio_and_messaging", ["driver", "wideRoad", "road"], True, True),
44+
])
45+
async def test_multi_camera(self, name, cameras, recv_audio, add_messaging):
46+
simple_answerer = SimpleAnswerProvider()
47+
offer_builder = WebRTCOfferBuilder(simple_answerer)
48+
for cam in cameras:
49+
offer_builder.offer_to_receive_video_stream(cam)
50+
if recv_audio:
51+
offer_builder.offer_to_receive_audio_stream()
52+
if add_messaging:
53+
offer_builder.add_messaging()
54+
stream = offer_builder.stream()
55+
56+
offer = await stream.start()
57+
self.assertTrue(stream.is_started)
58+
59+
try:
60+
async with asyncio.timeout(2):
61+
await stream.wait_for_connection()
62+
except asyncio.TimeoutError as e:
63+
self.fail("Timed out waiting for connection")
64+
self.assertTrue(stream.is_connected_and_ready)
65+
66+
self.assertEqual(stream.has_messaging_channel(), add_messaging)
67+
if stream.has_messaging_channel():
68+
channel = stream.get_messaging_channel()
69+
self.assertIsNotNone(channel)
70+
self.assertEqual(channel.readyState, "open")
71+
72+
self.assertEqual(stream.has_incoming_audio_track(), recv_audio)
73+
if stream.has_incoming_audio_track():
74+
track = stream.get_incoming_audio_track(False)
75+
self.assertIsNotNone(track)
76+
self.assertEqual(track.readyState, "live")
77+
self.assertEqual(track.kind, "audio")
78+
# test audio recv
79+
try:
80+
async with asyncio.timeout(1):
81+
await track.recv()
82+
except asyncio.TimeoutError as e:
83+
self.fail("Timed out waiting for audio frame")
84+
85+
for cam in cameras:
86+
self.assertTrue(stream.has_incoming_video_track(cam))
87+
if stream.has_incoming_video_track(cam):
88+
track = stream.get_incoming_video_track(cam, False)
89+
self.assertIsNotNone(track)
90+
self.assertEqual(track.readyState, "live")
91+
self.assertEqual(track.kind, "video")
92+
# test video recv
93+
try:
94+
async with asyncio.timeout(1):
95+
await stream.get_incoming_video_track(cam, False).recv()
96+
except asyncio.TimeoutError as e:
97+
self.fail("Timed out waiting for video frame")
98+
99+
await stream.stop()
100+
await simple_answerer.stream.stop()
101+
self.assertFalse(stream.is_started)
102+
self.assertFalse(stream.is_connected_and_ready)
103+
104+
105+
if __name__ == '__main__':
106+
unittest.main()

tests/test_track.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python3
2+
3+
import unittest
4+
5+
import aiortc
6+
7+
from teleoprtc.tracks import video_track_id, parse_video_track_id, TiciVideoStreamTrack, TiciTrackWrapper
8+
9+
10+
class TestTracks(unittest.TestCase):
11+
def test_track_id(self):
12+
expected_camera_type, expected_track_id = "driver", "test"
13+
track_id = video_track_id(expected_camera_type, expected_track_id)
14+
camera_type, track_id = parse_video_track_id(track_id)
15+
self.assertEqual(expected_camera_type, camera_type)
16+
self.assertEqual(expected_track_id, track_id)
17+
18+
def test_track_id_invalid(self):
19+
with self.assertRaises(ValueError):
20+
parse_video_track_id("test")
21+
22+
def test_tici_track_id(self):
23+
class VideoStream(TiciVideoStreamTrack):
24+
async def recv(self):
25+
raise NotImplementedError()
26+
27+
track = VideoStream("driver", 0.1)
28+
camera_type, _ = parse_video_track_id(track.id)
29+
self.assertEqual("driver", camera_type)
30+
31+
def test_tici_wrapper_id(self):
32+
track = TiciTrackWrapper("driver", aiortc.mediastreams.VideoStreamTrack())
33+
camera_type, _ = parse_video_track_id(track.id)
34+
self.assertEqual("driver", camera_type)
35+
36+
37+
if __name__ == '__main__':
38+
unittest.main()

0 commit comments

Comments
 (0)