From c6404fe1ccfca8fe76066bdbe06a695860fc946e Mon Sep 17 00:00:00 2001 From: Chanhee Lee Date: Mon, 28 Nov 2022 09:45:23 +0900 Subject: [PATCH] Add unit test cases for Android WebRTC module [Problem] There are not enough various test cases to verify Android WebRTC APIs. [Solution] Add unit and instrumented test cases for Android WebRTC. --- android/modules/webrtc/build.gradle | 27 +++++++ .../modules/webrtc/WebRTCInstrumentedTest.java | 6 ++ .../modules/webrtc/src/debug/AndroidManifest.xml | 20 ++++++ .../modules/webrtc/src/main/AndroidManifest.xml | 14 +++- .../com/samsung/android/modules/webrtc/WebRTC.java | 19 +++-- .../android/modules/webrtc/WebRTCServer.java | 3 + .../webrtc/src/test/java/android/util/Log.java | 23 ++++++ .../android/modules/webrtc/WebRTCUnitTest.java | 83 ++++++++++++++++++++++ build.gradle | 1 + 9 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 android/modules/webrtc/src/debug/AndroidManifest.xml create mode 100644 android/modules/webrtc/src/test/java/android/util/Log.java create mode 100644 android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/WebRTCUnitTest.java diff --git a/android/modules/webrtc/build.gradle b/android/modules/webrtc/build.gradle index e278215..2e27392 100644 --- a/android/modules/webrtc/build.gradle +++ b/android/modules/webrtc/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.android.library' + id 'jacoco' } android { @@ -13,6 +14,9 @@ android { consumerProguardFiles "consumer-rules.pro" } buildTypes { + debug { + testCoverageEnabled(true) + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -29,8 +33,31 @@ dependencies { implementation 'com.google.android.material:material:1.4.0' implementation 'org.webrtc:google-webrtc:1.0.32006' + testImplementation 'junit:junit:4.13.2' + testImplementation 'androidx.test:core:1.5.0' + testImplementation 'org.mockito:mockito-inline:3.4.0' + androidTestImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation project(path: ':android:aitt') } + +task jacocoReport(type: JacocoReport) { + group "Coverage" + description "Generate XML/HTML code coverage reports for coverage.ec" + + reports { + xml.enabled = true + html.enabled = true + } + + getSourceDirectories().setFrom("${project.projectDir}/src/main/java") + def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] + getClassDirectories().setFrom( + fileTree(dir: "${buildDir}/intermediates/javac/debug", excludes: fileFilter), + fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter)) + getExecutionData().setFrom( + fileTree(dir: "${buildDir}/outputs/code_coverage", includes: ['*.ec'])) +} diff --git a/android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java b/android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java index 08dd397..6f0e9e9 100644 --- a/android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java +++ b/android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java @@ -75,6 +75,7 @@ public class WebRTCInstrumentedTest { try { String ip = wifiIpAddress(); + Log.d(TAG, "Final WiFi IP = " + ip); Aitt serverSubscriber = new Aitt(appContext, AITT_WEBRTC_SERVER_ID, ip, true); serverSubscriber.connect(brokerIp, PORT); @@ -117,6 +118,10 @@ public class WebRTCInstrumentedTest { Looper.loop(); Log.i(TAG, "A looper is finished."); + + clientPublisherStream.disconnect(); + + serverSubscriberStream.stop(); } catch (Exception e) { fail("Failed testWebRTCBasicStreaming, (" + e + ")"); } @@ -128,6 +133,7 @@ public class WebRTCInstrumentedTest { LinkProperties linkProperties = connectivityManager.getLinkProperties(currentNetwork); for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { String targetIp = linkAddress.toString(); + Log.d(TAG, "Searched IP = " + targetIp); if (targetIp.contains("192.168")) { StringTokenizer tokenizer = new StringTokenizer(targetIp, "/"); if (tokenizer.hasMoreTokens()) diff --git a/android/modules/webrtc/src/debug/AndroidManifest.xml b/android/modules/webrtc/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..ca84321 --- /dev/null +++ b/android/modules/webrtc/src/debug/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/android/modules/webrtc/src/main/AndroidManifest.xml b/android/modules/webrtc/src/main/AndroidManifest.xml index 44e8f2e..ca84321 100644 --- a/android/modules/webrtc/src/main/AndroidManifest.xml +++ b/android/modules/webrtc/src/main/AndroidManifest.xml @@ -2,7 +2,19 @@ - + + + + + + + diff --git a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java index d7020dd..4246b55 100644 --- a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java +++ b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java @@ -101,6 +101,9 @@ public final class WebRTC { * @param appContext Application context creating webRTC instance */ public WebRTC(Context appContext) { + if (appContext == null) + throw new IllegalArgumentException("App context is null."); + this.appContext = appContext; this.isReceiver = false; } @@ -123,7 +126,10 @@ public final class WebRTC { * * @param cb aitt callback registered to receive a webrtc data */ - public void registerDataCallback(ReceiveDataCallback cb) { + void registerDataCallback(ReceiveDataCallback cb) { + if (cb == null) + throw new IllegalArgumentException("Callback is null."); + this.dataCallback = cb; } @@ -156,7 +162,7 @@ public final class WebRTC { /** * Method to establish a socket connection with peer node */ - public void connect() { + void connect() { initialize(); } @@ -170,6 +176,7 @@ public final class WebRTC { this.receiverIP = receiverIP; this.receiverPort = receiverPort; initialize(); + Log.i(TAG, "A WebRTC client is connected."); } /** @@ -180,6 +187,8 @@ public final class WebRTC { initializePeerConnectionFactory(); initializePeerConnections(); + Log.i(TAG, "Peer connections are initialized."); + if (!isReceiver) { createVideoTrack(); addVideoTrack(); @@ -427,7 +436,7 @@ public final class WebRTC { dataCallback.pushData(array); } else { String message = StandardCharsets.UTF_8.decode(buffer.data).toString(); - handlelargeMessage(message, buffer); + handleLargeMessage(message, buffer); } } }); @@ -453,7 +462,7 @@ public final class WebRTC { return factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver); } - private void handlelargeMessage(String message, DataChannel.Buffer buffer) { + private void handleLargeMessage(String message, DataChannel.Buffer buffer) { if (EOF_MESSAGE.equals(message)) { Log.d(TAG, "Byte array size: " + baos.size()); dataCallback.pushData(baos.toByteArray()); @@ -628,6 +637,7 @@ public final class WebRTC { createSocket(); invokeSendMessage(); + Log.i(TAG, "The SDP thread of WebRTC client started."); while (isRunning) { try { @@ -703,6 +713,7 @@ public final class WebRTC { } outStream = new ObjectOutputStream(socket.getOutputStream()); inputStream = new ObjectInputStream(socket.getInputStream()); + Log.i(TAG, "A WebRTC client socket and input/output streams are created."); } catch (Exception e) { Log.e(TAG, "Error during create socket", e); } diff --git a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java index b1dfd48..6548f32 100644 --- a/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java +++ b/android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java @@ -45,6 +45,9 @@ public final class WebRTCServer { * @param appContext Application context of the app creating WebRTCServer instance */ public WebRTCServer(Context appContext) { + if (appContext == null) + throw new IllegalArgumentException("App context is null."); + this.appContext = appContext; } diff --git a/android/modules/webrtc/src/test/java/android/util/Log.java b/android/modules/webrtc/src/test/java/android/util/Log.java new file mode 100644 index 0000000..69a8eca --- /dev/null +++ b/android/modules/webrtc/src/test/java/android/util/Log.java @@ -0,0 +1,23 @@ +package android.util; + +public class Log { + public static int d(String tag, String msg) { + System.out.println("DEBUG: " + tag + ": " + msg); + return 0; + } + + public static int i(String tag, String msg) { + System.out.println("INFO: " + tag + ": " + msg); + return 0; + } + + public static int w(String tag, String msg) { + System.out.println("WARN: " + tag + ": " + msg); + return 0; + } + + public static int e(String tag, String msg) { + System.out.println("ERROR: " + tag + ": " + msg); + return 0; + } +} diff --git a/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/WebRTCUnitTest.java b/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/WebRTCUnitTest.java new file mode 100644 index 0000000..80e7814 --- /dev/null +++ b/android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/WebRTCUnitTest.java @@ -0,0 +1,83 @@ +package com.samsung.android.modules.webrtc; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.util.Log; + +public class WebRTCUnitTest { + @Mock + private final Context context = mock(Context.class); + + private static MockedStatic mockedLog; + + @BeforeClass + public static void beforeClass() { + mockedLog = mockStatic(Log.class); + } + + @AfterClass + public static void afterClass() { + mockedLog.close(); + } + + @Test + public void testWebRTCServerConstructor_P() { + WebRTCServer webRTCServer = new WebRTCServer(context); + assertNotNull("WebRTCServer instance not null", webRTCServer); + } + + @Test(expected = IllegalArgumentException.class) + public void testWebRTCServerConstructor_N() throws IllegalArgumentException { + WebRTCServer webRTCServer = new WebRTCServer(null); + assertNotNull("WebRTCServer instance not null", webRTCServer); + } + + @Test + public void testWebRTCServerStart_P() { + WebRTCServer webRTCServer = new WebRTCServer(context); + webRTCServer.setDataCallback(frame -> { + }); + webRTCServer.start(); + } + + @Test + public void testWebRTCServerStart_N() { + when(Log.e(any(String.class), any(String.class))).thenReturn(any(Integer.class), any(Integer.class)); + WebRTCServer webRTCServer = new WebRTCServer(context); + webRTCServer.setDataCallback(null); + assertEquals("Fail to start a WebRTC server", -1, webRTCServer.start()); + } + + @Test + public void testWebRTCServerStop_P() { + WebRTCServer webRTCServer = new WebRTCServer(context); + webRTCServer.setDataCallback(frame -> { + }); + webRTCServer.start(); + + webRTCServer.stop(); + } + + @Test + public void testWebRTCConstructor_P() { + WebRTC webRTC = new WebRTC(context); + assertNotNull("WebRTC instance not null", webRTC); + } + + @Test(expected = IllegalArgumentException.class) + public void testWebRTCConstructor_N() throws IllegalArgumentException { + WebRTC webRTC = new WebRTC(null); + assertNotNull("WebRTC instance not null", webRTC); + } +} diff --git a/build.gradle b/build.gradle index a8ae137..f998374 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:4.2.0" + classpath "org.jacoco:org.jacoco.core:0.8.7" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } -- 2.7.4