Skip to content

Commit 2771ab9

Browse files
author
Antonis
committed
Fixed #657: Turn SignalingClient into normal class from singleton. Fixed #658: RCConnection localMediaType not properly set in audio call. Fixed #659: Document Integration Testing facilities. Fixed #655: Research and integrate/develop base facilities for asynchronous integration tests for SDK
1 parent 6328e0f commit 2771ab9

File tree

2 files changed

+37
-40
lines changed

2 files changed

+37
-40
lines changed

Examples/restcomm-olympus/app/src/androidTest/java/org/restcomm/android/olympus/IntegrationTests.java

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
@RunWith(AndroidJUnit4.class)
4141
/**
42-
* This class implements instrumented (i.e. on Device) integration tests facilities for the Restcomm Android SDK using JUnit4. Instrumented tests
42+
* This class implements instrumented integration test facilities (i.e. that run on device) for the Restcomm Android SDK using JUnit4. Instrumented tests
4343
* are more complex and more time consuming, but they need to be instrumented if we are to get realistic results. Integration tests are not meant to
4444
* replace UI or Unit tests, but to complement them by providing a way to really test our SDK's API top to bottom.
4545
*
@@ -53,26 +53,42 @@
5353
* Design and synchronization considerations
5454
*
5555
* The IT facilities are desined so that there are 3 threads involved in the tests (separate media threads are not relevant to this so they are kept out):
56-
* - The testing thread, where test cases run
57-
* - The API/Service thread where RCDevice/RCConnection calls are made, and relative callbacks are invoked
56+
* - The testing thread, where test cases run; this is the default thread where you test code runs unless told otherwise
57+
* - The API/Service thread where RCDevice/RCConnection calls are made, and relative callbacks are invoked (i.e. RCDevice.onInitialized())
5858
* - The Signaling thread that runs JAIN SIP user code and stack
5959
*
6060
* The reason we are using this setup is so that the testing thread that runs Awaitility framework (that provides facilities to wait
61-
* on various conditions) needs to be on a separate thread than API thread otherwise when API thread is blocked waiting for messages from Signaling thread
61+
* on various conditions) needs to be on a separate thread than API/Service thread otherwise when API thread is blocked waiting for messages from Signaling thread
6262
* the Awaitility framework wouldn't have a way to wait on conditions properly and the whole thing would break. Also, we want the testing thread to
6363
* be separate so that it's not affected by any timing issues on ANRs that the API thread might encounter. Finally we need to be able to write our tests
64-
* linearly so that they are easy to read and understand. So even though we could potentially keep Awaitility out and just use a single thread for testing and API
64+
* linearly so that they are easy to read and extend. So even though we could potentially keep Awaitility out and just use a single thread for testing and API
6565
* by adding more logic in the callback, the whole thing would become a mess as we'd have to keep state all over the place
6666
*
67-
* TODO: describe how Handlers & HandlerThreads are used, and why. Also describe how to avoid threading issues by always using the Main Looper for API thread
67+
* Adding/Extending an integration Test
6868
*
69-
* Extending the Integration Tests
69+
* Each integration test needs to do the following (please check deviceInitialize_Valid() test case for more details):
70+
* - Bind to the RCDevice Android Service and wait until it is connected using Awaitility condition variable (i.e. serviceConnected). This happens in the current (i.e. testing) thread
71+
* - Create an Android Handler and associate it with the Main Looper thread so that you can post work for it easily and linearly. Important: the Main Looper thread
72+
* is by default SEPARATE from the test thread, and the reason we use this Main Looper Thread specifically (instead of for example creating a new HandlerThread) is that inside
73+
* the API/Service this is the thread we explicitly use in some occasions to refer to the Service thread, so we need to stay consistent. For example the first thing
74+
* you will want to do in a test case is call RCDevice.initialize(). This needs to happen inside the API/Service thread
75+
* - Wait for RCDevice to be initialized on the testing thread. To do this you need to wait on an Awaitility condition variable (i.e. deviceInitialized) which is set when RCDevice.onInitialized() is called
76+
* on the API/Service thread.
77+
* - Assert that all is good by inspecting the context. Context is a HashMap that gets filled from the API/Service thread when a response is received with any information that we might need
78+
* like RCDevice object or status code
79+
* - After that you can continue posting actions in the API/Service thread, waiting for responses in the testing thread and asserting results sequentially.
7080
*
71-
* TODO: describe the assumptions made about the tests, like conditions variables and context, as well as synchronization between test & API threads that is not really implemented
81+
* Note that in the future if needed, we could potentially use another thread for API/Service instead of Main Looper thread. But if we do that we need to make sure to fix code
82+
* inside API/Service so that it doesn't use .getMainLooper() as it does right now. Otherwise we are bound to have synchronization issues.
83+
*
84+
* Notice that so far synchronization between test & API threads is not really implemented. So far it hasn't caused us any issues in the sense that context is not read by the test thread
85+
* until condition variable is set by the API/Service thread, which means that API/Service thread has finished writing it. Still the condition variable is accessed by both API/Service
86+
* thread and test thread, so we might need to remedy that at some point.
7287
*
7388
* Additional notes
7489
*
75-
* TODO: describe the issues with ServiceTestRule and why we removed it
90+
* Originally we used a ServiceTestRule to help us in waiting until the Android Service is ready, but we encountered some issues so we fell back to using Awaitility since it provides
91+
* pretty nice facilities on waiting for condition.
7692
*
7793
*/
7894
public class IntegrationTests implements RCDeviceListener, RCConnectionListener, ServiceConnection {
@@ -138,14 +154,15 @@ public void afterAction() {
138154
public void deviceInitialize_Valid() throws InterruptedException
139155
{
140156
InstrumentationRegistry.getTargetContext().bindService(new Intent(InstrumentationRegistry.getTargetContext(), RCDevice.class), this, Context.BIND_AUTO_CREATE);
141-
142157
await().atMost(SIGNALING_TIMEOUT, TimeUnit.SECONDS).until(fieldIn(this).ofType(boolean.class).andWithName("serviceConnected"), equalTo(true));
143158

144159
// Get the reference to the service, or you can call public methods on the binder directly.
145160
final RCDevice device = binder.getService();
146161

147-
HandlerThread clientHandlerThread = new HandlerThread("client-thread");
148-
clientHandlerThread.start();
162+
// Even though we don't use a HandlerThread now and use the Main Looper thread that is separate from the testing thread, let's keep this
163+
// code in case we want to change the threading logic later
164+
//HandlerThread clientHandlerThread = new HandlerThread("client-thread");
165+
//clientHandlerThread.start();
149166
//Handler clientHandler = new Handler(clientHandlerThread.getLooper());
150167
Handler clientHandler = new Handler(Looper.getMainLooper());
151168

@@ -191,8 +208,7 @@ public void run() {
191208
assertThat(((RCDevice)context.get("device")).getState()).isEqualTo(RCDevice.DeviceState.OFFLINE);
192209
assertThat(context.get("status-code")).isEqualTo(0);
193210

194-
clientHandlerThread.quit();
195-
//assertThat(deviceReleased).isTrue();
211+
//clientHandlerThread.quit();
196212

197213
InstrumentationRegistry.getTargetContext().unbindService(this);
198214
}
@@ -209,9 +225,6 @@ public void deviceInitialize_EncryptedSignaling_Valid() throws InterruptedExcept
209225
// Get the reference to the service, or you can call public methods on the binder directly.
210226
final RCDevice device = binder.getService();
211227

212-
HandlerThread clientHandlerThread = new HandlerThread("client-thread");
213-
clientHandlerThread.start();
214-
//Handler clientHandler = new Handler(clientHandlerThread.getLooper());
215228
Handler clientHandler = new Handler(Looper.getMainLooper());
216229

217230
clientHandler.post(new Runnable() {
@@ -246,7 +259,6 @@ public void run() {
246259
assertThat(((RCDevice)context.get("device")).getState()).isEqualTo(RCDevice.DeviceState.READY);
247260
assertThat(context.get("status-code")).isEqualTo(0);
248261

249-
250262
clientHandler.post(new Runnable() {
251263
@Override
252264
public void run() {
@@ -258,10 +270,6 @@ public void run() {
258270
assertThat(((RCDevice)context.get("device")).getState()).isEqualTo(RCDevice.DeviceState.OFFLINE);
259271
assertThat(context.get("status-code")).isEqualTo(0);
260272

261-
262-
clientHandlerThread.quit();
263-
//assertThat(deviceReleased).isTrue();
264-
265273
InstrumentationRegistry.getTargetContext().unbindService(this);
266274
}
267275

@@ -277,9 +285,6 @@ public void deviceMakeAudioCall_Valid() throws InterruptedException
277285
// Get the reference to the service, or you can call public methods on the binder directly.
278286
final RCDevice device = binder.getService();
279287

280-
HandlerThread clientHandlerThread = new HandlerThread("client-thread");
281-
clientHandlerThread.start();
282-
//Handler clientHandler = new Handler(clientHandlerThread.getLooper());
283288
Handler clientHandler = new Handler(Looper.getMainLooper());
284289

285290
clientHandler.post(new Runnable() {
@@ -356,7 +361,6 @@ public void run() {
356361
assertThat(((RCDevice)context.get("device")).getState()).isEqualTo(RCDevice.DeviceState.OFFLINE);
357362
assertThat(context.get("status-code")).isEqualTo(0);
358363

359-
clientHandlerThread.quit();
360364
assertThat(deviceReleased).isTrue();
361365

362366
InstrumentationRegistry.getTargetContext().unbindService(this);
@@ -374,9 +378,6 @@ public void deviceMakeVideoCall_Valid() throws InterruptedException
374378
// Get the reference to the service, or you can call public methods on the binder directly.
375379
final RCDevice device = binder.getService();
376380

377-
HandlerThread clientHandlerThread = new HandlerThread("client-thread");
378-
clientHandlerThread.start();
379-
//Handler clientHandler = new Handler(clientHandlerThread.getLooper());
380381
Handler clientHandler = new Handler(Looper.getMainLooper());
381382

382383
clientHandler.post(new Runnable() {
@@ -457,10 +458,6 @@ public void run() {
457458
assertThat(((RCDevice)context.get("device")).getState()).isEqualTo(RCDevice.DeviceState.OFFLINE);
458459
assertThat(context.get("status-code")).isEqualTo(0);
459460

460-
461-
clientHandlerThread.quit();
462-
assertThat(deviceReleased).isTrue();
463-
464461
InstrumentationRegistry.getTargetContext().unbindService(this);
465462
}
466463

restcomm.android.sdk/src/main/java/org/restcomm/android/sdk/RCConnection.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,13 +1314,6 @@ public void run()
13141314

13151315
private void initializeVideo(boolean videoEnabled, PercentFrameLayout localRenderLayout, PercentFrameLayout remoteRenderLayout)
13161316
{
1317-
if (videoEnabled) {
1318-
localMediaType = ConnectionMediaType.AUDIO_VIDEO;
1319-
}
1320-
else {
1321-
localMediaType = ConnectionMediaType.AUDIO;
1322-
}
1323-
13241317
if (localRenderLayout == null ||remoteRenderLayout == null) {
13251318
return;
13261319
}
@@ -1457,6 +1450,13 @@ private void initializeWebrtc(boolean videoEnabled,
14571450
iceConnected = false;
14581451
signalingParameters = null;
14591452

1453+
if (videoEnabled) {
1454+
localMediaType = ConnectionMediaType.AUDIO_VIDEO;
1455+
}
1456+
else {
1457+
localMediaType = ConnectionMediaType.AUDIO;
1458+
}
1459+
14601460
if (localRenderLayout != null && remoteRenderLayout != null) {
14611461
initializeVideo(videoEnabled, localRenderLayout, remoteRenderLayout);
14621462
}

0 commit comments

Comments
 (0)