Add unit test cases for Android WebRTC module
authorChanhee Lee <ch2102.lee@samsung.com>
Mon, 28 Nov 2022 00:45:23 +0000 (09:45 +0900)
committerChanhee Lee <ch2102.lee@samsung.com>
Mon, 12 Dec 2022 09:25:20 +0000 (18:25 +0900)
[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
android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java
android/modules/webrtc/src/debug/AndroidManifest.xml [new file with mode: 0644]
android/modules/webrtc/src/main/AndroidManifest.xml
android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java
android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTCServer.java
android/modules/webrtc/src/test/java/android/util/Log.java [new file with mode: 0644]
android/modules/webrtc/src/test/java/com/samsung/android/modules/webrtc/WebRTCUnitTest.java [new file with mode: 0644]
build.gradle

index e278215..2e27392 100644 (file)
@@ -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']))
+}
index 08dd397..6f0e9e9 100644 (file)
@@ -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 (file)
index 0000000..ca84321
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.samsung.android.modules.webrtc">
+
+    <uses-feature
+        android:glEsVersion="0x00020000"
+        android:required="true" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission
+        android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="29" />
+    <uses-permission
+        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        android:maxSdkVersion="29" />
+
+    <application android:requestLegacyExternalStorage="true" />
+
+</manifest>
index 44e8f2e..ca84321 100644 (file)
@@ -2,7 +2,19 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.samsung.android.modules.webrtc">
 
-    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+    <uses-feature
+        android:glEsVersion="0x00020000"
+        android:required="true" />
+
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission
+        android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="29" />
+    <uses-permission
+        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        android:maxSdkVersion="29" />
+
+    <application android:requestLegacyExternalStorage="true" />
+
 </manifest>
index d7020dd..4246b55 100644 (file)
@@ -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);
             }
index b1dfd48..6548f32 100644 (file)
@@ -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 (file)
index 0000000..69a8eca
--- /dev/null
@@ -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 (file)
index 0000000..80e7814
--- /dev/null
@@ -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<Log> 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);
+    }
+}
index a8ae137..f998374 100644 (file)
@@ -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
     }