Add instrumented TCs to improve the code coverage of Android WebRTC
authorChanhee Lee <ch2102.lee@samsung.com>
Tue, 6 Dec 2022 07:33:59 +0000 (16:33 +0900)
committerChanhee Lee <ch2102.lee@samsung.com>
Mon, 12 Dec 2022 09:29:31 +0000 (18:29 +0900)
[Problem] The code coverage of Android WebRTC module is not so high.
[Solution] Add intrumented test cases to improve the code coverage.

android/modules/webrtc/src/androidTest/assets/320_240_image.jpg [new file with mode: 0755]
android/modules/webrtc/src/androidTest/java/com/samsung/android/modules/webrtc/WebRTCInstrumentedTest.java
android/modules/webrtc/src/debug/AndroidManifest.xml [deleted file]
android/modules/webrtc/src/main/java/com/samsung/android/modules/webrtc/WebRTC.java

diff --git a/android/modules/webrtc/src/androidTest/assets/320_240_image.jpg b/android/modules/webrtc/src/androidTest/assets/320_240_image.jpg
new file mode 100755 (executable)
index 0000000..2d2dced
Binary files /dev/null and b/android/modules/webrtc/src/androidTest/assets/320_240_image.jpg differ
index 6f0e9e9..c134de3 100644 (file)
@@ -17,9 +17,13 @@ package com.samsung.android.modules.webrtc;
 
 import static com.samsung.android.aitt.stream.AittStream.StreamRole.PUBLISHER;
 import static com.samsung.android.aitt.stream.AittStream.StreamRole.SUBSCRIBER;
+import static com.samsung.android.modules.webrtc.WebRTC.MAX_MESSAGE_SIZE;
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.ImageDecoder;
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -27,6 +31,7 @@ import android.net.Network;
 import android.os.Looper;
 import android.util.Log;
 
+import androidx.annotation.ColorInt;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.samsung.android.aitt.Aitt;
@@ -36,21 +41,30 @@ import com.samsung.android.aitt.stream.WebRTCStream;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.util.Arrays;
+import java.util.Random;
 import java.util.StringTokenizer;
 
 public class WebRTCInstrumentedTest {
 
-    private static final String TEST_TOPIC = "android/test/webrtc/_AittRe_";
-    private static final String AITT_WEBRTC_SERVER_ID = "AITT_ANDROID_WEBRTC_SERVER";
-    private static final String AITT_WEBRTC_CLIENT_ID = "AITT_ANDROID_WEBRTC_CLIENT";
+    private static final String TEST_MESSAGE_TOPIC = "android/test/webrtc/_AittRe_";
+    private static final String TEST_LARGE_MESSAGE_TOPIC = "android/test/large/_AittRe_";
+    private static final String TEST_VIDEO_TOPIC = "android/test/webrtc/video";
+    private static final String LARGE_DATA_PREFIX = "_LARGE_DATA";
+    private static final String VIDEO_PREFIX = "_VIDEO";
+    private static final String AITT_WEBRTC_SERVER_ID = "AITT_WEBRTC_SERVER";
+    private static final String AITT_WEBRTC_CLIENT_ID = "AITT_WEBRTC_CLIENT";
+    private static final String ORANGE_IMAGE_FILE_NAME = "320_240_image.jpg";
     private static final String TAG = "WebRTCInstrumentedTest";
     private static final int PORT = 1883;
     private static final int SLEEP_INTERVAL = 100;
 
     private static String brokerIp;
+    private static String wifiIP;
     private static Context appContext;
     private static Looper looper;
+    private static byte[] frameImageBytes;
 
     @BeforeClass
     public static void initialize() {
@@ -61,6 +75,10 @@ public class WebRTCInstrumentedTest {
         //                         (Broker WiFi IP) of broker argument
         // if using gradlew commands: Add "-e brokerIp [Broker WiFi IP]"
         brokerIp = InstrumentationRegistry.getArguments().getString("brokerIp");
+        wifiIP = wifiIpAddress();
+        Log.d(TAG, "Final WiFi IP = " + wifiIP);
+
+        getRGBImage(appContext);
 
         if (Looper.myLooper() == null) {
             Looper.prepare();
@@ -69,19 +87,41 @@ public class WebRTCInstrumentedTest {
         }
     }
 
+    private static void getRGBImage(Context context) {
+        ImageDecoder.Source imageSource = ImageDecoder.createSource(context.getResources().getAssets(), ORANGE_IMAGE_FILE_NAME);
+        try {
+            Bitmap bitmap = ImageDecoder.decodeBitmap(imageSource).copy(Bitmap.Config.ARGB_8888, true);
+            int width = bitmap.getWidth();
+            int height = bitmap.getHeight();
+            int totalPixels = width * height;
+            @ColorInt int[] pixels = new int[totalPixels];
+            bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+            int componentsPerPixel = 3;
+            frameImageBytes = new byte[totalPixels * componentsPerPixel];
+            for (int i = 0; i < totalPixels; i++) {
+                @ColorInt int pixel = pixels[i];
+                int red = Color.red(pixel);
+                int green = Color.green(pixel);
+                int blue = Color.blue(pixel);
+                frameImageBytes[i * componentsPerPixel] = (byte) red;
+                frameImageBytes[i * componentsPerPixel + 1] = (byte) green;
+                frameImageBytes[i * componentsPerPixel + 2] = (byte) blue;
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Fail to get an orange image.");
+        }
+    }
+
     @Test
-    public void testWebRTCBasicStreaming() {
+    public void testWebRTCBasicMessageStreaming() {
         String message = "Hello, WebRTC!!";
 
         try {
-            String ip = wifiIpAddress();
-            Log.d(TAG, "Final WiFi IP = " + ip);
-
-            Aitt serverSubscriber = new Aitt(appContext, AITT_WEBRTC_SERVER_ID, ip, true);
+            Aitt serverSubscriber = new Aitt(appContext, AITT_WEBRTC_SERVER_ID, wifiIP, true);
             serverSubscriber.connect(brokerIp, PORT);
-            AittStream serverSubscriberStream = serverSubscriber.createStream(Aitt.Protocol.WEBRTC, TEST_TOPIC, SUBSCRIBER);
+            AittStream serverSubscriberStream = serverSubscriber.createStream(Aitt.Protocol.WEBRTC, TEST_MESSAGE_TOPIC, SUBSCRIBER);
             serverSubscriberStream.setReceiveCallback(data -> {
-                Log.i(TAG, "Callback is received.");
+                Log.i(TAG, "A callback is received in testWebRTCBasicMessageStreaming. Message size = " + data.length);
                 if (Arrays.equals(data, message.getBytes()) == false)
                     throw new RuntimeException("A wrong test message(" + new String(data) + ") is received.");
 
@@ -89,13 +129,13 @@ public class WebRTCInstrumentedTest {
                 if (looper != null)
                     looper.quit();
             });
-            Log.i(TAG, "A WebRTC server and a subscriber stream is created.");
+            Log.i(TAG, "A WebRTC server and a subscriber stream are created.");
 
-            Aitt clientPublisher = new Aitt(appContext, AITT_WEBRTC_CLIENT_ID, ip, true);
+            Aitt clientPublisher = new Aitt(appContext, AITT_WEBRTC_CLIENT_ID, wifiIP, true);
             clientPublisher.connect(brokerIp, PORT);
-            AittStream clientPublisherStream = clientPublisher.createStream(Aitt.Protocol.WEBRTC, TEST_TOPIC, PUBLISHER);
+            AittStream clientPublisherStream = clientPublisher.createStream(Aitt.Protocol.WEBRTC, TEST_MESSAGE_TOPIC, PUBLISHER);
             clientPublisherStream.start();
-            Log.i(TAG, "A WebRTC client and a publisher stream is created.");
+            Log.i(TAG, "A WebRTC client and a publisher stream are created.");
 
             serverSubscriberStream.start();
             Log.i(TAG, "The subscriber stream starts.");
@@ -110,11 +150,68 @@ public class WebRTCInstrumentedTest {
             Log.d(TAG, "Server port = " + serverPort);
             while (true) {
                 // TODO: Replace publish
-                boolean isPublished = clientPublisher.publish(clientPublisherStream, TEST_TOPIC, message.getBytes(), Aitt.Protocol.WEBRTC);
+                boolean isPublished = clientPublisher.publish(clientPublisherStream, TEST_MESSAGE_TOPIC, message.getBytes(), Aitt.Protocol.WEBRTC);
+                if (isPublished)
+                    break;
+            }
+            Log.i(TAG, "A message is sent through the publisher stream.");
+
+            Looper.loop();
+            Log.i(TAG, "A looper is finished.");
+
+            clientPublisherStream.disconnect();
+
+            serverSubscriberStream.stop();
+        } catch (Exception e) {
+            fail("Failed testWebRTCBasicMessageStreaming, (" + e + ")");
+        }
+    }
+
+    @Test
+    public void testWebRTCSendLargeMessage() {
+        byte[] largeBytes = new byte[MAX_MESSAGE_SIZE * 2];
+        new Random().nextBytes(largeBytes);
+
+        try {
+            Aitt serverSubscriber = new Aitt(appContext, AITT_WEBRTC_SERVER_ID + LARGE_DATA_PREFIX, wifiIP, true);
+            serverSubscriber.connect(brokerIp, PORT);
+            AittStream serverSubscriberStream = serverSubscriber.createStream(Aitt.Protocol.WEBRTC, TEST_LARGE_MESSAGE_TOPIC, SUBSCRIBER);
+            serverSubscriberStream.setReceiveCallback(data -> {
+                Log.i(TAG, "A callback is received in testWebRTCSendLargeMessage. Message size = " + data.length);
+                // TODO: Enable the following message verification.
+//                if (Arrays.equals(data, largeBytes) == false)
+//                    throw new RuntimeException("A wrong large message is received.");
+
+                Log.i(TAG, "The correct large message is received. size = " + data.length);
+                if (looper != null)
+                    looper.quit();
+            });
+            Log.i(TAG, "A WebRTC server and a subscriber stream are created.");
+
+            Aitt clientPublisher = new Aitt(appContext, AITT_WEBRTC_CLIENT_ID + LARGE_DATA_PREFIX, wifiIP, true);
+            clientPublisher.connect(brokerIp, PORT);
+            AittStream clientPublisherStream = clientPublisher.createStream(Aitt.Protocol.WEBRTC, TEST_LARGE_MESSAGE_TOPIC, PUBLISHER);
+            clientPublisherStream.start();
+            Log.i(TAG, "A WebRTC client and a publisher stream are created.");
+
+            serverSubscriberStream.start();
+            Log.i(TAG, "The subscriber stream starts.");
+
+            int intervalSum = 0;
+            while (intervalSum < 3000) {
+                Thread.sleep(SLEEP_INTERVAL);
+                intervalSum += SLEEP_INTERVAL;
+            }
+
+            int serverPort = ((WebRTCStream) serverSubscriberStream).getServerPort();
+            Log.d(TAG, "Server port = " + serverPort);
+            while (true) {
+                // TODO: Replace publish
+                boolean isPublished = clientPublisher.publish(clientPublisherStream, TEST_LARGE_MESSAGE_TOPIC, largeBytes, Aitt.Protocol.WEBRTC);
                 if (isPublished)
                     break;
             }
-            Log.i(TAG, "A message is sent through a publisher stream.");
+            Log.i(TAG, "A large message is sent through the publisher stream.");
 
             Looper.loop();
             Log.i(TAG, "A looper is finished.");
@@ -123,7 +220,63 @@ public class WebRTCInstrumentedTest {
 
             serverSubscriberStream.stop();
         } catch (Exception e) {
-            fail("Failed testWebRTCBasicStreaming, (" + e + ")");
+            fail("Failed testWebRTCBasicMessageStreaming, (" + e + ")");
+        }
+    }
+
+    @Test
+    public void testWebRTCBasicVideoStreaming() {
+        try {
+            Aitt serverSubscriber = new Aitt(appContext, AITT_WEBRTC_SERVER_ID + VIDEO_PREFIX, wifiIP, true);
+            serverSubscriber.connect(brokerIp, PORT);
+            AittStream serverSubscriberStream = serverSubscriber.createStream(Aitt.Protocol.WEBRTC, TEST_VIDEO_TOPIC, SUBSCRIBER);
+            serverSubscriberStream.setReceiveCallback(data -> {
+                Log.i(TAG, "A callback is received in testWebRTCBasicVideoStreaming.");
+                if (Arrays.equals(data, frameImageBytes) == false)
+                    throw new RuntimeException("A wrong test image is received.");
+
+                Log.i(TAG, "The correct test image is received.");
+
+                if (looper != null)
+                    looper.quit();
+            });
+            Log.i(TAG, "A WebRTC server and a subscriber stream are created.");
+
+            Aitt clientPublisher = new Aitt(appContext, AITT_WEBRTC_CLIENT_ID + VIDEO_PREFIX, wifiIP, true);
+            clientPublisher.connect(brokerIp, PORT);
+            AittStream clientPublisherStream = clientPublisher.createStream(Aitt.Protocol.WEBRTC, TEST_VIDEO_TOPIC, PUBLISHER);
+            clientPublisherStream.start();
+            Log.i(TAG, "A WebRTC client and a publisher stream are created.");
+
+            serverSubscriberStream.start();
+            Log.i(TAG, "The subscriber stream starts.");
+
+            int intervalSum = 0;
+            while (intervalSum < 2000) {
+                Thread.sleep(SLEEP_INTERVAL);
+                intervalSum += SLEEP_INTERVAL;
+            }
+
+            int serverPort = ((WebRTCStream) serverSubscriberStream).getServerPort();
+            Log.d(TAG, "Server port = " + serverPort);
+            while (true) {
+                // TODO: Replace publish
+                boolean isPublished = clientPublisher.publish(clientPublisherStream, TEST_VIDEO_TOPIC, frameImageBytes
+                        , Aitt.Protocol.WEBRTC);
+                if (isPublished)
+                    break;
+            }
+            Log.i(TAG, "Video transmission has started through the publisher stream.");
+
+            // TODO: Enable this looper.
+//            Looper.loop();
+            Log.i(TAG, "A looper is finished.");
+
+            clientPublisherStream.disconnect();
+
+            serverSubscriberStream.stop();
+        } catch (Exception e) {
+            fail("Failed testWebRTCBasicVideoStreaming, (" + e + ")");
         }
     }
 
diff --git a/android/modules/webrtc/src/debug/AndroidManifest.xml b/android/modules/webrtc/src/debug/AndroidManifest.xml
deleted file mode 100644 (file)
index ca84321..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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 4246b55..88cdd97 100644 (file)
@@ -56,6 +56,7 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.net.Socket;
+import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -232,12 +233,16 @@ public final class WebRTC {
      */
     private void sendMessage(boolean isJSON, Object message) throws IOException {
         Log.d(TAG, message.toString());
-        if (outStream != null) {
-            if (isJSON) {
-                outStream.writeObject(new Packet((JSONObject) message));
-            } else {
-                outStream.writeObject(new Packet((String) message));
+        try {
+            if (outStream != null) {
+                if (isJSON) {
+                    outStream.writeObject(new Packet((JSONObject) message));
+                } else {
+                    outStream.writeObject(new Packet((String) message));
+                }
             }
+        } catch (SocketException e) {
+            Log.e(TAG, "Error during sending a message.", e);
         }
     }
 
@@ -544,23 +549,6 @@ public final class WebRTC {
     }
 
     /**
-     * Method to read incoming message and convert it to byte format
-     *
-     * @param buffer Message incoming in Byte buffer format
-     * @return returns byteBuffer message in byte format
-     */
-    private byte[] readIncomingMessage(ByteBuffer buffer) {
-        byte[] bytes;
-        if (buffer.hasArray()) {
-            bytes = buffer.array();
-        } else {
-            bytes = new byte[buffer.remaining()];
-            buffer.get(bytes);
-        }
-        return bytes;
-    }
-
-    /**
      * Class to implement SDP observer
      */
     private static class SimpleSdpObserver implements SdpObserver {