From eaada07913e2abf413e7bbea5af68295bde0f205 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 11 Aug 2021 15:35:36 +0900 Subject: [PATCH 1/2] Refactor NsdManagerTest Refactor the test to use Kotlin, and move some repeated code to submethods. Bug: 190249673 Test: atest NsdManagerTest Change-Id: Iee7273080d5d29f1d364ac0a77e017edf9b36051 --- .../src/android/net/cts/NsdManagerTest.java | 594 ------------------ .../net/src/android/net/cts/NsdManagerTest.kt | 422 +++++++++++++ 2 files changed, 422 insertions(+), 594 deletions(-) delete mode 100644 tests/cts/net/src/android/net/cts/NsdManagerTest.java create mode 100644 tests/cts/net/src/android/net/cts/NsdManagerTest.kt diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.java b/tests/cts/net/src/android/net/cts/NsdManagerTest.java deleted file mode 100644 index 2bcfdc315b..0000000000 --- a/tests/cts/net/src/android/net/cts/NsdManagerTest.java +++ /dev/null @@ -1,594 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.cts; - -import android.content.Context; -import android.net.nsd.NsdManager; -import android.net.nsd.NsdServiceInfo; -import android.platform.test.annotations.AppModeFull; -import android.test.AndroidTestCase; -import android.util.Log; - -import java.io.IOException; -import java.net.ServerSocket; -import java.util.Arrays; -import java.util.Random; -import java.util.List; -import java.util.ArrayList; - -@AppModeFull(reason = "Socket cannot bind in instant app mode") -public class NsdManagerTest extends AndroidTestCase { - - private static final String TAG = "NsdManagerTest"; - private static final String SERVICE_TYPE = "_nmt._tcp"; - private static final int TIMEOUT = 2000; - - private static final boolean DBG = false; - - NsdManager mNsdManager; - - NsdManager.RegistrationListener mRegistrationListener; - NsdManager.DiscoveryListener mDiscoveryListener; - NsdManager.ResolveListener mResolveListener; - private NsdServiceInfo mResolvedService; - - public NsdManagerTest() { - initRegistrationListener(); - initDiscoveryListener(); - initResolveListener(); - } - - private void initRegistrationListener() { - mRegistrationListener = new NsdManager.RegistrationListener() { - @Override - public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { - setEvent("onRegistrationFailed", errorCode); - } - - @Override - public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { - setEvent("onUnregistrationFailed", errorCode); - } - - @Override - public void onServiceRegistered(NsdServiceInfo serviceInfo) { - setEvent("onServiceRegistered", serviceInfo); - } - - @Override - public void onServiceUnregistered(NsdServiceInfo serviceInfo) { - setEvent("onServiceUnregistered", serviceInfo); - } - }; - } - - private void initDiscoveryListener() { - mDiscoveryListener = new NsdManager.DiscoveryListener() { - @Override - public void onStartDiscoveryFailed(String serviceType, int errorCode) { - setEvent("onStartDiscoveryFailed", errorCode); - } - - @Override - public void onStopDiscoveryFailed(String serviceType, int errorCode) { - setEvent("onStopDiscoveryFailed", errorCode); - } - - @Override - public void onDiscoveryStarted(String serviceType) { - NsdServiceInfo info = new NsdServiceInfo(); - info.setServiceType(serviceType); - setEvent("onDiscoveryStarted", info); - } - - @Override - public void onDiscoveryStopped(String serviceType) { - NsdServiceInfo info = new NsdServiceInfo(); - info.setServiceType(serviceType); - setEvent("onDiscoveryStopped", info); - } - - @Override - public void onServiceFound(NsdServiceInfo serviceInfo) { - setEvent("onServiceFound", serviceInfo); - } - - @Override - public void onServiceLost(NsdServiceInfo serviceInfo) { - setEvent("onServiceLost", serviceInfo); - } - }; - } - - private void initResolveListener() { - mResolveListener = new NsdManager.ResolveListener() { - @Override - public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { - setEvent("onResolveFailed", errorCode); - } - - @Override - public void onServiceResolved(NsdServiceInfo serviceInfo) { - mResolvedService = serviceInfo; - setEvent("onServiceResolved", serviceInfo); - } - }; - } - - - - private final class EventData { - EventData(String callbackName, NsdServiceInfo info) { - mCallbackName = callbackName; - mSucceeded = true; - mErrorCode = 0; - mInfo = info; - } - EventData(String callbackName, int errorCode) { - mCallbackName = callbackName; - mSucceeded = false; - mErrorCode = errorCode; - mInfo = null; - } - private final String mCallbackName; - private final boolean mSucceeded; - private final int mErrorCode; - private final NsdServiceInfo mInfo; - } - - private final List mEventCache = new ArrayList(); - - private void setEvent(String callbackName, int errorCode) { - if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode)); - EventData eventData = new EventData(callbackName, errorCode); - synchronized (mEventCache) { - mEventCache.add(eventData); - mEventCache.notify(); - } - } - - private void setEvent(String callbackName, NsdServiceInfo info) { - if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName()); - EventData eventData = new EventData(callbackName, info); - synchronized (mEventCache) { - mEventCache.add(eventData); - mEventCache.notify(); - } - } - - void clearEventCache() { - synchronized(mEventCache) { - mEventCache.clear(); - } - } - - int eventCacheSize() { - synchronized(mEventCache) { - return mEventCache.size(); - } - } - - private int mWaitId = 0; - private EventData waitForCallback(String callbackName) { - - synchronized(mEventCache) { - - mWaitId ++; - if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId)); - - try { - long startTime = android.os.SystemClock.uptimeMillis(); - long elapsedTime = 0; - int index = 0; - while (elapsedTime < TIMEOUT ) { - // first check if we've received that event - for (; index < mEventCache.size(); index++) { - EventData e = mEventCache.get(index); - if (e.mCallbackName.equals(callbackName)) { - if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId)); - return e; - } - } - - // Not yet received, just wait - mEventCache.wait(TIMEOUT - elapsedTime); - elapsedTime = android.os.SystemClock.uptimeMillis() - startTime; - } - // we exited the loop because of TIMEOUT; fail the call - if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId)); - return null; - } catch (InterruptedException e) { - return null; // wait timed out! - } - } - } - - private EventData waitForNewEvents() throws InterruptedException { - if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId)); - - long startTime = android.os.SystemClock.uptimeMillis(); - long elapsedTime = 0; - synchronized (mEventCache) { - int index = mEventCache.size(); - while (elapsedTime < TIMEOUT ) { - // first check if we've received that event - for (; index < mEventCache.size(); index++) { - EventData e = mEventCache.get(index); - return e; - } - - // Not yet received, just wait - mEventCache.wait(TIMEOUT - elapsedTime); - elapsedTime = android.os.SystemClock.uptimeMillis() - startTime; - } - } - - return null; - } - - private String mServiceName; - - @Override - public void setUp() throws Exception { - super.setUp(); - if (DBG) Log.d(TAG, "Setup test ..."); - mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE); - - Random rand = new Random(); - mServiceName = new String("NsdTest"); - for (int i = 0; i < 4; i++) { - mServiceName = mServiceName + String.valueOf(rand.nextInt(10)); - } - } - - @Override - public void tearDown() throws Exception { - if (DBG) Log.d(TAG, "Tear down test ..."); - super.tearDown(); - } - - public void testNDSManager() throws Exception { - EventData lastEvent = null; - - if (DBG) Log.d(TAG, "Starting test ..."); - - NsdServiceInfo si = new NsdServiceInfo(); - si.setServiceType(SERVICE_TYPE); - si.setServiceName(mServiceName); - - byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2}; - String String256 = "1_________2_________3_________4_________5_________6_________" + - "7_________8_________9_________10________11________12________13________" + - "14________15________16________17________18________19________20________" + - "21________22________23________24________25________123456"; - - // Illegal attributes - try { - si.setAttribute(null, (String) null); - fail("Could set null key"); - } catch (IllegalArgumentException e) { - // expected - } - - try { - si.setAttribute("", (String) null); - fail("Could set empty key"); - } catch (IllegalArgumentException e) { - // expected - } - - try { - si.setAttribute(String256, (String) null); - fail("Could set key with 255 characters"); - } catch (IllegalArgumentException e) { - // expected - } - - try { - si.setAttribute("key", String256.substring(3)); - fail("Could set key+value combination with more than 255 characters"); - } catch (IllegalArgumentException e) { - // expected - } - - try { - si.setAttribute("key", String256.substring(4)); - fail("Could set key+value combination with 255 characters"); - } catch (IllegalArgumentException e) { - // expected - } - - try { - si.setAttribute(new String(new byte[]{0x19}), (String) null); - fail("Could set key with invalid character"); - } catch (IllegalArgumentException e) { - // expected - } - - try { - si.setAttribute("=", (String) null); - fail("Could set key with invalid character"); - } catch (IllegalArgumentException e) { - // expected - } - - try { - si.setAttribute(new String(new byte[]{0x7F}), (String) null); - fail("Could set key with invalid character"); - } catch (IllegalArgumentException e) { - // expected - } - - // Allowed attributes - si.setAttribute("booleanAttr", (String) null); - si.setAttribute("keyValueAttr", "value"); - si.setAttribute("keyEqualsAttr", "="); - si.setAttribute(" whiteSpaceKeyValueAttr ", " value "); - si.setAttribute("binaryDataAttr", testByteArray); - si.setAttribute("nullBinaryDataAttr", (byte[]) null); - si.setAttribute("emptyBinaryDataAttr", new byte[]{}); - si.setAttribute("longkey", String256.substring(9)); - - ServerSocket socket; - int localPort; - - try { - socket = new ServerSocket(0); - localPort = socket.getLocalPort(); - si.setPort(localPort); - } catch (IOException e) { - if (DBG) Log.d(TAG, "Could not open a local socket"); - assertTrue(false); - return; - } - - if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort)); - - clearEventCache(); - - mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener); - lastEvent = waitForCallback("onServiceRegistered"); // id = 1 - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - assertTrue(eventCacheSize() == 1); - - // We may not always get the name that we tried to register; - // This events tells us the name that was registered. - String registeredName = lastEvent.mInfo.getServiceName(); - si.setServiceName(registeredName); - - clearEventCache(); - - mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, - mDiscoveryListener); - - // Expect discovery started - lastEvent = waitForCallback("onDiscoveryStarted"); // id = 2 - - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - - // Remove this event, so accounting becomes easier later - synchronized (mEventCache) { - mEventCache.remove(lastEvent); - } - - // Expect a service record to be discovered (and filter the ones - // that are unrelated to this test) - boolean found = false; - for (int i = 0; i < 32; i++) { - - lastEvent = waitForCallback("onServiceFound"); // id = 3 - if (lastEvent == null) { - // no more onServiceFound events are being reported! - break; - } - - assertTrue(lastEvent.mSucceeded); - - if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " + - lastEvent.mInfo.getServiceName()); - - if (lastEvent.mInfo.getServiceName().equals(registeredName)) { - // Save it, as it will get overwritten with new serviceFound events - si = lastEvent.mInfo; - found = true; - } - - // Remove this event from the event cache, so it won't be found by subsequent - // calls to waitForCallback - synchronized (mEventCache) { - mEventCache.remove(lastEvent); - } - } - - assertTrue(found); - - // We've removed all serviceFound events, and we've removed the discoveryStarted - // event as well, so now the event cache should be empty! - assertTrue(eventCacheSize() == 0); - - // Resolve the service - clearEventCache(); - mNsdManager.resolveService(si, mResolveListener); - lastEvent = waitForCallback("onServiceResolved"); // id = 4 - - assertNotNull(mResolvedService); - - // Check Txt attributes - assertEquals(8, mResolvedService.getAttributes().size()); - assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr")); - assertNull(mResolvedService.getAttributes().get("booleanAttr")); - assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr"))); - assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr"))); - assertEquals(" value ", new String(mResolvedService.getAttributes() - .get(" whiteSpaceKeyValueAttr "))); - assertEquals(String256.substring(9), new String(mResolvedService.getAttributes() - .get("longkey"))); - assertTrue(Arrays.equals(testByteArray, - mResolvedService.getAttributes().get("binaryDataAttr"))); - assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr")); - assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr")); - assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr")); - assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr")); - - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - - if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " + - String.valueOf(lastEvent.mInfo.getPort())); - - assertTrue(lastEvent.mInfo.getPort() == localPort); - assertTrue(eventCacheSize() == 1); - - checkForAdditionalEvents(); - clearEventCache(); - - // Unregister the service - mNsdManager.unregisterService(mRegistrationListener); - lastEvent = waitForCallback("onServiceUnregistered"); // id = 5 - - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - - // Expect a callback for service lost - lastEvent = waitForCallback("onServiceLost"); // id = 6 - - assertTrue(lastEvent != null); - assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName)); - - // Register service again to see if we discover it - checkForAdditionalEvents(); - clearEventCache(); - - si = new NsdServiceInfo(); - si.setServiceType(SERVICE_TYPE); - si.setServiceName(mServiceName); - si.setPort(localPort); - - // Create a new registration listener and register same service again - initRegistrationListener(); - - mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener); - - lastEvent = waitForCallback("onServiceRegistered"); // id = 7 - - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - - registeredName = lastEvent.mInfo.getServiceName(); - - // Expect a record to be discovered - // Expect a service record to be discovered (and filter the ones - // that are unrelated to this test) - found = false; - for (int i = 0; i < 32; i++) { - - lastEvent = waitForCallback("onServiceFound"); // id = 8 - if (lastEvent == null) { - // no more onServiceFound events are being reported! - break; - } - - assertTrue(lastEvent.mSucceeded); - - if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " + - lastEvent.mInfo.getServiceName()); - - if (lastEvent.mInfo.getServiceName().equals(registeredName)) { - // Save it, as it will get overwritten with new serviceFound events - si = lastEvent.mInfo; - found = true; - } - - // Remove this event from the event cache, so it won't be found by subsequent - // calls to waitForCallback - synchronized (mEventCache) { - mEventCache.remove(lastEvent); - } - } - - assertTrue(found); - - // Resolve the service - clearEventCache(); - mNsdManager.resolveService(si, mResolveListener); - lastEvent = waitForCallback("onServiceResolved"); // id = 9 - - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - - if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " + - lastEvent.mInfo.getServiceName()); - - assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName)); - - assertNotNull(mResolvedService); - - // Check that we don't have any TXT records - assertEquals(0, mResolvedService.getAttributes().size()); - - checkForAdditionalEvents(); - clearEventCache(); - - mNsdManager.stopServiceDiscovery(mDiscoveryListener); - lastEvent = waitForCallback("onDiscoveryStopped"); // id = 10 - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - assertTrue(checkCacheSize(1)); - - checkForAdditionalEvents(); - clearEventCache(); - - mNsdManager.unregisterService(mRegistrationListener); - - lastEvent = waitForCallback("onServiceUnregistered"); // id = 11 - assertTrue(lastEvent != null); - assertTrue(lastEvent.mSucceeded); - assertTrue(checkCacheSize(1)); - } - - boolean checkCacheSize(int size) { - synchronized (mEventCache) { - int cacheSize = mEventCache.size(); - if (cacheSize != size) { - Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize); - for (int i = 0; i < cacheSize; i++) { - EventData e = mEventCache.get(i); - String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : ""; - Log.d(TAG, "eventName is " + e.mCallbackName + sname); - } - } - return (cacheSize == size); - } - } - - boolean checkForAdditionalEvents() { - try { - EventData e = waitForNewEvents(); - if (e != null) { - String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : ""; - Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname); - } - return (e == null); - } - catch (InterruptedException ex) { - return false; - } - } -} - diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt new file mode 100644 index 0000000000..8daf72089f --- /dev/null +++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.cts + +import android.net.nsd.NsdManager +import android.net.nsd.NsdManager.DiscoveryListener +import android.net.nsd.NsdManager.ResolveListener +import android.net.nsd.NsdServiceInfo +import android.os.SystemClock +import android.platform.test.annotations.AppModeFull +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import java.net.ServerSocket +import java.nio.charset.StandardCharsets +import java.util.Random +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.fail + +private const val TAG = "NsdManagerTest" +private const val SERVICE_TYPE = "_nmt._tcp" +private const val TIMEOUT = 2000 +private const val DBG = false + +@AppModeFull(reason = "Socket cannot bind in instant app mode") +@RunWith(AndroidJUnit4::class) +class NsdManagerTest { + private val context by lazy { InstrumentationRegistry.getInstrumentation().context } + private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) } + private val serviceName = "NsdTest%04d".format(Random().nextInt(1000)) + + private val registrationListener = object : NsdManager.RegistrationListener { + override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { + setEvent("onRegistrationFailed", errorCode) + } + + override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { + setEvent("onUnregistrationFailed", errorCode) + } + + override fun onServiceRegistered(serviceInfo: NsdServiceInfo) { + setEvent("onServiceRegistered", serviceInfo) + } + + override fun onServiceUnregistered(serviceInfo: NsdServiceInfo) { + setEvent("onServiceUnregistered", serviceInfo) + } + } + + private val discoveryListener = object : DiscoveryListener { + override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) { + setEvent("onStartDiscoveryFailed", errorCode) + } + + override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { + setEvent("onStopDiscoveryFailed", errorCode) + } + + override fun onDiscoveryStarted(serviceType: String) { + val info = NsdServiceInfo() + info.serviceType = serviceType + setEvent("onDiscoveryStarted", info) + } + + override fun onDiscoveryStopped(serviceType: String) { + val info = NsdServiceInfo() + info.serviceType = serviceType + setEvent("onDiscoveryStopped", info) + } + + override fun onServiceFound(serviceInfo: NsdServiceInfo) { + setEvent("onServiceFound", serviceInfo) + } + + override fun onServiceLost(serviceInfo: NsdServiceInfo) { + setEvent("onServiceLost", serviceInfo) + } + } + + private inner class TestResolveListener : ResolveListener { + var resolvedService: NsdServiceInfo? = null + override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { + setEvent("onResolveFailed", errorCode) + } + + override fun onServiceResolved(serviceInfo: NsdServiceInfo) { + resolvedService = serviceInfo + setEvent("onServiceResolved", serviceInfo) + } + } + + private class EventData { + constructor(callbackName: String, info: NsdServiceInfo?) { + this.callbackName = callbackName + succeeded = true + errorCode = 0 + this.info = info + } + + constructor(callbackName: String, errorCode: Int) { + this.callbackName = callbackName + succeeded = false + this.errorCode = errorCode + info = null + } + + val callbackName: String + val succeeded: Boolean + private val errorCode: Int + val info: NsdServiceInfo? + } + + private val eventCache = ArrayList() + private fun setEvent(callbackName: String, errorCode: Int) { + if (DBG) Log.d(TAG, "$callbackName failed with $errorCode") + val eventData = EventData(callbackName, errorCode) + synchronized(eventCache) { + eventCache.add(eventData) + eventCache.notify() + } + } + + private fun setEvent(callbackName: String, info: NsdServiceInfo) { + if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.serviceName) + val eventData = EventData(callbackName, info) + synchronized(eventCache) { + eventCache.add(eventData) + eventCache.notify() + } + } + + fun clearEventCache() { + synchronized(eventCache) { eventCache.clear() } + } + + fun eventCacheSize(): Int { + synchronized(eventCache) { return eventCache.size } + } + + private var waitId = 0 + private fun waitForCallback(callbackName: String): EventData? { + synchronized(eventCache) { + waitId++ + if (DBG) Log.d(TAG, "Waiting for $callbackName, id=$waitId") + val startTime = SystemClock.uptimeMillis() + var elapsedTime = 0L + while (elapsedTime < TIMEOUT) { + // first check if we've received that event + eventCache.find { it.callbackName == callbackName }?.let { + if (DBG) Log.d(TAG, "exiting wait id=$waitId") + return it + } + + // Not yet received, just wait + try { + eventCache.wait(TIMEOUT - elapsedTime) + } catch (e: InterruptedException) { + return null + } + elapsedTime = SystemClock.uptimeMillis() - startTime + } + // we exited the loop because of TIMEOUT; fail the call + if (DBG) Log.d(TAG, "timed out waiting id=$waitId") + return null + } + } + + private fun waitForNewEvents(): EventData? { + if (DBG) Log.d(TAG, "Waiting for a bit, id=$waitId") + val startTime = SystemClock.uptimeMillis() + var elapsedTime = 0L + synchronized(eventCache) { + val index = eventCache.size + while (elapsedTime < TIMEOUT) { + // first check if we've received that event + if (index < eventCache.size) { + return eventCache[index] + } + + // Not yet received, just wait + eventCache.wait(TIMEOUT - elapsedTime) + elapsedTime = SystemClock.uptimeMillis() - startTime + } + } + return null + } + + @Test + fun testNsdManager() { + val si = NsdServiceInfo() + si.serviceType = SERVICE_TYPE + si.serviceName = serviceName + val testByteArray = byteArrayOf(-128, 127, 2, 1, 0, 1, 2) + val string256 = "1_________2_________3_________4_________5_________6_________" + + "7_________8_________9_________10________11________12________13________" + + "14________15________16________17________18________19________20________" + + "21________22________23________24________25________123456" + + // Illegal attributes + assertFailsWith("Could set null key") { + si.setAttribute(null, null as String?) + } + assertFailsWith("Could set empty key") { + si.setAttribute("", null as String?) + } + assertFailsWith("Could set key with 255 characters") { + si.setAttribute(string256, null as String?) + } + assertFailsWith( + "Could set key+value combination with more than 255 characters") { + si.setAttribute("key", string256.substring(3)) + } + assertFailsWith( + "Could set key+value combination with 255 characters") { + si.setAttribute("key", string256.substring(4)) + } + assertFailsWith("Could set key with invalid character") { + si.setAttribute("\u0019", null as String?) + } + assertFailsWith("Could set key with invalid character") { + si.setAttribute("=", null as String?) + } + assertFailsWith("Could set key with invalid character") { + si.setAttribute("\u007f", null as String?) + } + + // Allowed attributes + si.setAttribute("booleanAttr", null as String?) + si.setAttribute("keyValueAttr", "value") + si.setAttribute("keyEqualsAttr", "=") + si.setAttribute(" whiteSpaceKeyValueAttr ", " value ") + si.setAttribute("binaryDataAttr", testByteArray) + si.setAttribute("nullBinaryDataAttr", null as ByteArray?) + si.setAttribute("emptyBinaryDataAttr", byteArrayOf()) + si.setAttribute("longkey", string256.substring(9)) + val socket = ServerSocket(0) + val localPort = socket.localPort + si.port = localPort + if (DBG) Log.d(TAG, "Port = $localPort") + clearEventCache() + + val registeredName = registerService(si) + + assertEquals(1, eventCacheSize()) + clearEventCache() + nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener) + + // Expect discovery started + var lastEvent = waitForCallback("onDiscoveryStarted") + assertNotNull(lastEvent) + assertTrue(lastEvent.succeeded) + + // Remove this event, so accounting becomes easier later + synchronized(eventCache) { eventCache.remove(lastEvent) } + + // Expect a service record to be discovered + val foundInfo = waitForServiceDiscovered(registeredName) + + // We've removed all serviceFound events, and we've removed the discoveryStarted + // event as well, so now the event cache should be empty! + assertEquals(0, eventCacheSize()) + + val resolvedService = resolveService(foundInfo) + + // Check Txt attributes + assertEquals(8, resolvedService.attributes.size) + assertTrue(resolvedService.attributes.containsKey("booleanAttr")) + assertNull(resolvedService.attributes["booleanAttr"]) + assertEquals("value", resolvedService.attributes["keyValueAttr"].utf8ToString()) + assertEquals("=", resolvedService.attributes["keyEqualsAttr"].utf8ToString()) + assertEquals(" value ", + resolvedService.attributes[" whiteSpaceKeyValueAttr "].utf8ToString()) + assertEquals(string256.substring(9), resolvedService.attributes["longkey"].utf8ToString()) + assertArrayEquals(testByteArray, resolvedService.attributes["binaryDataAttr"]) + assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr")) + assertNull(resolvedService.attributes["nullBinaryDataAttr"]) + assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr")) + assertNull(resolvedService.attributes["emptyBinaryDataAttr"]) + if (DBG) Log.d(TAG, "id = $waitId: Port = ${lastEvent.info?.port}") + assertEquals(localPort, resolvedService.port) + assertEquals(1, eventCacheSize()) + checkForAdditionalEvents() + clearEventCache() + + // Unregister the service + nsdManager.unregisterService(registrationListener) + lastEvent = waitForCallback("onServiceUnregistered") + assertNotNull(lastEvent) + assertTrue(lastEvent.succeeded) + + // Expect a callback for service lost + lastEvent = waitForCallback("onServiceLost") + assertNotNull(lastEvent) + assertEquals(registeredName, lastEvent.info?.serviceName) + + // Register service again to see if we discover it + checkForAdditionalEvents() + clearEventCache() + val si2 = NsdServiceInfo() + si2.serviceType = SERVICE_TYPE + si2.serviceName = serviceName + si2.port = localPort + val registeredName2 = registerService(si2) + + // Expect a record to be discovered + // Expect a service record to be discovered (and filter the ones + // that are unrelated to this test) + val foundInfo2 = waitForServiceDiscovered(registeredName2) + + // Resolve the service + clearEventCache() + val resolvedService2 = resolveService(foundInfo2) + + // Check that we don't have any TXT records + assertEquals(0, resolvedService2.attributes.size) + checkForAdditionalEvents() + clearEventCache() + nsdManager.stopServiceDiscovery(discoveryListener) + lastEvent = waitForCallback("onDiscoveryStopped") + assertNotNull(lastEvent) + assertTrue(lastEvent.succeeded) + checkCacheSize(1) + checkForAdditionalEvents() + clearEventCache() + nsdManager.unregisterService(registrationListener) + lastEvent = waitForCallback("onServiceUnregistered") + assertNotNull(lastEvent) + assertTrue(lastEvent.succeeded) + checkCacheSize(1) + } + + /** + * Register a service and return its registered name. + */ + private fun registerService(si: NsdServiceInfo): String { + nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationListener) + + // We may not always get the name that we tried to register; + // This events tells us the name that was registered. + val cb = waitForCallback("onServiceRegistered") + assertNotNull(cb) + assertTrue(cb.succeeded) + return cb.info?.serviceName ?: fail("Missing event info") + } + + private fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo { + var foundInfo: NsdServiceInfo? = null + repeat(32) { + val event = waitForCallback("onServiceFound") ?: return@repeat + assertTrue(event.succeeded) + if (DBG) Log.d(TAG, "id = $waitId: ServiceName = ${event.info?.serviceName}") + if (event.info?.serviceName == serviceName) { + // Save it, as it will get overwritten with new serviceFound events + foundInfo = event.info + } + + // Remove this event from the event cache, so it won't be found by subsequent + // calls to waitForCallback + synchronized(eventCache) { eventCache.remove(event) } + } + return foundInfo ?: fail("Service not discovered") + } + + private fun resolveService(discoveredInfo: NsdServiceInfo): NsdServiceInfo { + val resolveListener = TestResolveListener() + nsdManager.resolveService(discoveredInfo, resolveListener) + val resolvedCb = waitForCallback("onServiceResolved") + assertNotNull(resolvedCb) + assertTrue(resolvedCb.succeeded) + if (DBG) Log.d(TAG, "id = $waitId: ServiceName = ${resolvedCb.info?.serviceName}") + assertEquals(discoveredInfo.serviceName, resolvedCb.info?.serviceName) + + return resolveListener.resolvedService ?: fail("Missing resolved service") + } + + fun checkCacheSize(size: Int) { + synchronized(eventCache) { + if (size != eventCache.size) { + fail("Expected size $size, found event list [${ + eventCache.joinToString(", ") { + "eventName: ${it.callbackName}, serviceName ${it.info?.serviceName}" + } + }]") + } + } + } + + fun checkForAdditionalEvents(): Boolean { + val e = waitForNewEvents() ?: return true + Log.d(TAG, "ignoring unexpected event ${e.callbackName} (${e.info?.serviceName})") + return false + } +} + +private fun ByteArray?.utf8ToString(): String { + if (this == null) return "" + return String(this, StandardCharsets.UTF_8) +} + +// TODO: migrate legacy java-style implementation to newer utils like RecorderCallback +private fun Any.wait(timeout: Long) = (this as Object).wait(timeout) +private fun Any.notify() = (this as Object).notify() \ No newline at end of file From 0f17237773b4b86842b8c37bf473513c62e2c50e Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 11 Aug 2021 19:24:54 +0900 Subject: [PATCH 2/2] Use recording callbacks in NsdManagerTest Refactor the test to use recording callbacks based on ArrayTrackRecord, which allow removing the test's own logic to poll for events. Bug: 190249673 Test: atest NsdManagerTest --rerun-until-failure 20 Change-Id: Iad0b0d52271b13954c0193b3b9d4307349a39443 --- .../net/src/android/net/cts/NsdManagerTest.kt | 423 +++++++----------- 1 file changed, 163 insertions(+), 260 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt index 8daf72089f..9307c27854 100644 --- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt +++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt @@ -15,15 +15,29 @@ */ package android.net.cts +import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted +import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped +import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound +import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost +import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StartDiscoveryFailed +import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StopDiscoveryFailed +import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed +import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered +import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered +import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.UnregistrationFailed +import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolveFailed +import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ServiceResolved import android.net.nsd.NsdManager import android.net.nsd.NsdManager.DiscoveryListener +import android.net.nsd.NsdManager.RegistrationListener import android.net.nsd.NsdManager.ResolveListener import android.net.nsd.NsdServiceInfo -import android.os.SystemClock import android.platform.test.annotations.AppModeFull import android.util.Log import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 +import com.android.net.module.util.ArrayTrackRecord +import com.android.net.module.util.TrackRecord import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -35,11 +49,12 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertTrue import kotlin.test.fail private const val TAG = "NsdManagerTest" private const val SERVICE_TYPE = "_nmt._tcp" -private const val TIMEOUT = 2000 +private const val TIMEOUT_MS = 2000L private const val DBG = false @AppModeFull(reason = "Socket cannot bind in instant app mode") @@ -49,160 +64,128 @@ class NsdManagerTest { private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) } private val serviceName = "NsdTest%04d".format(Random().nextInt(1000)) - private val registrationListener = object : NsdManager.RegistrationListener { - override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { - setEvent("onRegistrationFailed", errorCode) - } + private interface NsdEvent + private open class NsdRecord private constructor( + private val history: ArrayTrackRecord + ) : TrackRecord by history { + constructor() : this(ArrayTrackRecord()) - override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { - setEvent("onUnregistrationFailed", errorCode) - } + val nextEvents = history.newReadHead() - override fun onServiceRegistered(serviceInfo: NsdServiceInfo) { - setEvent("onServiceRegistered", serviceInfo) - } + inline fun expectCallbackEventually( + crossinline predicate: (V) -> Boolean = { true } + ): V = nextEvents.poll(TIMEOUT_MS) { e -> e is V && predicate(e) } as V? + ?: fail("Callback for ${V::class.java.simpleName} not seen after $TIMEOUT_MS ms") - override fun onServiceUnregistered(serviceInfo: NsdServiceInfo) { - setEvent("onServiceUnregistered", serviceInfo) + inline fun expectCallback(): V { + val nextEvent = nextEvents.poll(TIMEOUT_MS) + assertNotNull(nextEvent, "No callback received after $TIMEOUT_MS ms") + assertTrue(nextEvent is V, "Expected ${V::class.java.simpleName} but got " + + nextEvent.javaClass.simpleName) + return nextEvent } } - private val discoveryListener = object : DiscoveryListener { - override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) { - setEvent("onStartDiscoveryFailed", errorCode) + private class NsdRegistrationRecord : RegistrationListener, + NsdRecord() { + sealed class RegistrationEvent : NsdEvent { + abstract val serviceInfo: NsdServiceInfo + + data class RegistrationFailed( + override val serviceInfo: NsdServiceInfo, + val errorCode: Int + ) : RegistrationEvent() + + data class UnregistrationFailed( + override val serviceInfo: NsdServiceInfo, + val errorCode: Int + ) : RegistrationEvent() + + data class ServiceRegistered(override val serviceInfo: NsdServiceInfo) + : RegistrationEvent() + data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo) + : RegistrationEvent() } - override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { - setEvent("onStopDiscoveryFailed", errorCode) + override fun onRegistrationFailed(si: NsdServiceInfo, err: Int) { + add(RegistrationFailed(si, err)) + } + + override fun onUnregistrationFailed(si: NsdServiceInfo, err: Int) { + add(UnregistrationFailed(si, err)) + } + + override fun onServiceRegistered(si: NsdServiceInfo) { + add(ServiceRegistered(si)) + } + + override fun onServiceUnregistered(si: NsdServiceInfo) { + add(ServiceUnregistered(si)) + } + } + + private class NsdDiscoveryRecord : DiscoveryListener, + NsdRecord() { + sealed class DiscoveryEvent : NsdEvent { + data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int) + : DiscoveryEvent() + + data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int) + : DiscoveryEvent() + + data class DiscoveryStarted(val serviceType: String) : DiscoveryEvent() + data class DiscoveryStopped(val serviceType: String) : DiscoveryEvent() + data class ServiceFound(val serviceInfo: NsdServiceInfo) : DiscoveryEvent() + data class ServiceLost(val serviceInfo: NsdServiceInfo) : DiscoveryEvent() + } + + override fun onStartDiscoveryFailed(serviceType: String, err: Int) { + add(StartDiscoveryFailed(serviceType, err)) + } + + override fun onStopDiscoveryFailed(serviceType: String, err: Int) { + add(StopDiscoveryFailed(serviceType, err)) } override fun onDiscoveryStarted(serviceType: String) { - val info = NsdServiceInfo() - info.serviceType = serviceType - setEvent("onDiscoveryStarted", info) + add(DiscoveryStarted(serviceType)) } override fun onDiscoveryStopped(serviceType: String) { - val info = NsdServiceInfo() - info.serviceType = serviceType - setEvent("onDiscoveryStopped", info) + add(DiscoveryStopped(serviceType)) } - override fun onServiceFound(serviceInfo: NsdServiceInfo) { - setEvent("onServiceFound", serviceInfo) + override fun onServiceFound(si: NsdServiceInfo) { + add(ServiceFound(si)) } - override fun onServiceLost(serviceInfo: NsdServiceInfo) { - setEvent("onServiceLost", serviceInfo) + override fun onServiceLost(si: NsdServiceInfo) { + add(ServiceLost(si)) + } + + fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo { + return expectCallbackEventually { + it.serviceInfo.serviceName == serviceName + }.serviceInfo } } - private inner class TestResolveListener : ResolveListener { - var resolvedService: NsdServiceInfo? = null - override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { - setEvent("onResolveFailed", errorCode) + private class NsdResolveRecord : ResolveListener, + NsdRecord() { + sealed class ResolveEvent : NsdEvent { + data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) + : ResolveEvent() + + data class ServiceResolved(val serviceInfo: NsdServiceInfo) : ResolveEvent() } - override fun onServiceResolved(serviceInfo: NsdServiceInfo) { - resolvedService = serviceInfo - setEvent("onServiceResolved", serviceInfo) - } - } - - private class EventData { - constructor(callbackName: String, info: NsdServiceInfo?) { - this.callbackName = callbackName - succeeded = true - errorCode = 0 - this.info = info + override fun onResolveFailed(si: NsdServiceInfo, err: Int) { + add(ResolveFailed(si, err)) } - constructor(callbackName: String, errorCode: Int) { - this.callbackName = callbackName - succeeded = false - this.errorCode = errorCode - info = null + override fun onServiceResolved(si: NsdServiceInfo) { + add(ServiceResolved(si)) } - - val callbackName: String - val succeeded: Boolean - private val errorCode: Int - val info: NsdServiceInfo? - } - - private val eventCache = ArrayList() - private fun setEvent(callbackName: String, errorCode: Int) { - if (DBG) Log.d(TAG, "$callbackName failed with $errorCode") - val eventData = EventData(callbackName, errorCode) - synchronized(eventCache) { - eventCache.add(eventData) - eventCache.notify() - } - } - - private fun setEvent(callbackName: String, info: NsdServiceInfo) { - if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.serviceName) - val eventData = EventData(callbackName, info) - synchronized(eventCache) { - eventCache.add(eventData) - eventCache.notify() - } - } - - fun clearEventCache() { - synchronized(eventCache) { eventCache.clear() } - } - - fun eventCacheSize(): Int { - synchronized(eventCache) { return eventCache.size } - } - - private var waitId = 0 - private fun waitForCallback(callbackName: String): EventData? { - synchronized(eventCache) { - waitId++ - if (DBG) Log.d(TAG, "Waiting for $callbackName, id=$waitId") - val startTime = SystemClock.uptimeMillis() - var elapsedTime = 0L - while (elapsedTime < TIMEOUT) { - // first check if we've received that event - eventCache.find { it.callbackName == callbackName }?.let { - if (DBG) Log.d(TAG, "exiting wait id=$waitId") - return it - } - - // Not yet received, just wait - try { - eventCache.wait(TIMEOUT - elapsedTime) - } catch (e: InterruptedException) { - return null - } - elapsedTime = SystemClock.uptimeMillis() - startTime - } - // we exited the loop because of TIMEOUT; fail the call - if (DBG) Log.d(TAG, "timed out waiting id=$waitId") - return null - } - } - - private fun waitForNewEvents(): EventData? { - if (DBG) Log.d(TAG, "Waiting for a bit, id=$waitId") - val startTime = SystemClock.uptimeMillis() - var elapsedTime = 0L - synchronized(eventCache) { - val index = eventCache.size - while (elapsedTime < TIMEOUT) { - // first check if we've received that event - if (index < eventCache.size) { - return eventCache[index] - } - - // Not yet received, just wait - eventCache.wait(TIMEOUT - elapsedTime) - elapsedTime = SystemClock.uptimeMillis() - startTime - } - } - return null } @Test @@ -210,38 +193,30 @@ class NsdManagerTest { val si = NsdServiceInfo() si.serviceType = SERVICE_TYPE si.serviceName = serviceName + // Test binary data with various bytes val testByteArray = byteArrayOf(-128, 127, 2, 1, 0, 1, 2) + // Test string data with 256 characters (25 blocks of 10 characters + 6) val string256 = "1_________2_________3_________4_________5_________6_________" + "7_________8_________9_________10________11________12________13________" + "14________15________16________17________18________19________20________" + "21________22________23________24________25________123456" // Illegal attributes - assertFailsWith("Could set null key") { - si.setAttribute(null, null as String?) - } - assertFailsWith("Could set empty key") { - si.setAttribute("", null as String?) - } - assertFailsWith("Could set key with 255 characters") { - si.setAttribute(string256, null as String?) - } - assertFailsWith( - "Could set key+value combination with more than 255 characters") { - si.setAttribute("key", string256.substring(3)) - } - assertFailsWith( - "Could set key+value combination with 255 characters") { - si.setAttribute("key", string256.substring(4)) - } - assertFailsWith("Could set key with invalid character") { - si.setAttribute("\u0019", null as String?) - } - assertFailsWith("Could set key with invalid character") { - si.setAttribute("=", null as String?) - } - assertFailsWith("Could set key with invalid character") { - si.setAttribute("\u007f", null as String?) + listOf( + Triple(null, null, "null key"), + Triple("", null, "empty key"), + Triple(string256, null, "key with 256 characters"), + Triple("key", string256.substring(3), + "key+value combination with more than 255 characters"), + Triple("key", string256.substring(4), "key+value combination with 255 characters"), + Triple("\u0019", null, "key with invalid character"), + Triple("=", null, "key with invalid character"), + Triple("\u007f", null, "key with invalid character") + ).forEach { + assertFailsWith( + "Setting invalid ${it.third} unexpectedly succeeded") { + si.setAttribute(it.first, it.second) + } } // Allowed attributes @@ -257,28 +232,18 @@ class NsdManagerTest { val localPort = socket.localPort si.port = localPort if (DBG) Log.d(TAG, "Port = $localPort") - clearEventCache() - val registeredName = registerService(si) + val registrationRecord = NsdRegistrationRecord() + val registeredInfo = registerService(registrationRecord, si) - assertEquals(1, eventCacheSize()) - clearEventCache() - nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener) + val discoveryRecord = NsdDiscoveryRecord() + nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord) // Expect discovery started - var lastEvent = waitForCallback("onDiscoveryStarted") - assertNotNull(lastEvent) - assertTrue(lastEvent.succeeded) - - // Remove this event, so accounting becomes easier later - synchronized(eventCache) { eventCache.remove(lastEvent) } + discoveryRecord.expectCallback() // Expect a service record to be discovered - val foundInfo = waitForServiceDiscovered(registeredName) - - // We've removed all serviceFound events, and we've removed the discoveryStarted - // event as well, so now the event cache should be empty! - assertEquals(0, eventCacheSize()) + val foundInfo = discoveryRecord.waitForServiceDiscovered(registeredInfo.serviceName) val resolvedService = resolveService(foundInfo) @@ -296,119 +261,61 @@ class NsdManagerTest { assertNull(resolvedService.attributes["nullBinaryDataAttr"]) assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr")) assertNull(resolvedService.attributes["emptyBinaryDataAttr"]) - if (DBG) Log.d(TAG, "id = $waitId: Port = ${lastEvent.info?.port}") assertEquals(localPort, resolvedService.port) - assertEquals(1, eventCacheSize()) - checkForAdditionalEvents() - clearEventCache() // Unregister the service - nsdManager.unregisterService(registrationListener) - lastEvent = waitForCallback("onServiceUnregistered") - assertNotNull(lastEvent) - assertTrue(lastEvent.succeeded) + nsdManager.unregisterService(registrationRecord) + registrationRecord.expectCallback() // Expect a callback for service lost - lastEvent = waitForCallback("onServiceLost") - assertNotNull(lastEvent) - assertEquals(registeredName, lastEvent.info?.serviceName) + discoveryRecord.expectCallbackEventually { + it.serviceInfo.serviceName == serviceName + } - // Register service again to see if we discover it - checkForAdditionalEvents() - clearEventCache() + // Register service again to see if NsdManager can discover it val si2 = NsdServiceInfo() si2.serviceType = SERVICE_TYPE si2.serviceName = serviceName si2.port = localPort - val registeredName2 = registerService(si2) + val registrationRecord2 = NsdRegistrationRecord() + val registeredInfo2 = registerService(registrationRecord2, si2) - // Expect a record to be discovered // Expect a service record to be discovered (and filter the ones // that are unrelated to this test) - val foundInfo2 = waitForServiceDiscovered(registeredName2) + val foundInfo2 = discoveryRecord.waitForServiceDiscovered(registeredInfo2.serviceName) // Resolve the service - clearEventCache() val resolvedService2 = resolveService(foundInfo2) - // Check that we don't have any TXT records + // Check that the resolved service doesn't have any TXT records assertEquals(0, resolvedService2.attributes.size) - checkForAdditionalEvents() - clearEventCache() - nsdManager.stopServiceDiscovery(discoveryListener) - lastEvent = waitForCallback("onDiscoveryStopped") - assertNotNull(lastEvent) - assertTrue(lastEvent.succeeded) - checkCacheSize(1) - checkForAdditionalEvents() - clearEventCache() - nsdManager.unregisterService(registrationListener) - lastEvent = waitForCallback("onServiceUnregistered") - assertNotNull(lastEvent) - assertTrue(lastEvent.succeeded) - checkCacheSize(1) + + nsdManager.stopServiceDiscovery(discoveryRecord) + + discoveryRecord.expectCallbackEventually() + + nsdManager.unregisterService(registrationRecord2) + registrationRecord2.expectCallback() } /** - * Register a service and return its registered name. + * Register a service and return its registration record. */ - private fun registerService(si: NsdServiceInfo): String { - nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationListener) - + private fun registerService(record: NsdRegistrationRecord, si: NsdServiceInfo): NsdServiceInfo { + nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record) // We may not always get the name that we tried to register; // This events tells us the name that was registered. - val cb = waitForCallback("onServiceRegistered") - assertNotNull(cb) - assertTrue(cb.succeeded) - return cb.info?.serviceName ?: fail("Missing event info") - } - - private fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo { - var foundInfo: NsdServiceInfo? = null - repeat(32) { - val event = waitForCallback("onServiceFound") ?: return@repeat - assertTrue(event.succeeded) - if (DBG) Log.d(TAG, "id = $waitId: ServiceName = ${event.info?.serviceName}") - if (event.info?.serviceName == serviceName) { - // Save it, as it will get overwritten with new serviceFound events - foundInfo = event.info - } - - // Remove this event from the event cache, so it won't be found by subsequent - // calls to waitForCallback - synchronized(eventCache) { eventCache.remove(event) } - } - return foundInfo ?: fail("Service not discovered") + val cb = record.expectCallback() + return cb.serviceInfo } private fun resolveService(discoveredInfo: NsdServiceInfo): NsdServiceInfo { - val resolveListener = TestResolveListener() - nsdManager.resolveService(discoveredInfo, resolveListener) - val resolvedCb = waitForCallback("onServiceResolved") - assertNotNull(resolvedCb) - assertTrue(resolvedCb.succeeded) - if (DBG) Log.d(TAG, "id = $waitId: ServiceName = ${resolvedCb.info?.serviceName}") - assertEquals(discoveredInfo.serviceName, resolvedCb.info?.serviceName) + val record = NsdResolveRecord() + nsdManager.resolveService(discoveredInfo, record) + val resolvedCb = record.expectCallback() + assertEquals(discoveredInfo.serviceName, resolvedCb.serviceInfo.serviceName) - return resolveListener.resolvedService ?: fail("Missing resolved service") - } - - fun checkCacheSize(size: Int) { - synchronized(eventCache) { - if (size != eventCache.size) { - fail("Expected size $size, found event list [${ - eventCache.joinToString(", ") { - "eventName: ${it.callbackName}, serviceName ${it.info?.serviceName}" - } - }]") - } - } - } - - fun checkForAdditionalEvents(): Boolean { - val e = waitForNewEvents() ?: return true - Log.d(TAG, "ignoring unexpected event ${e.callbackName} (${e.info?.serviceName})") - return false + return resolvedCb.serviceInfo } } @@ -416,7 +323,3 @@ private fun ByteArray?.utf8ToString(): String { if (this == null) return "" return String(this, StandardCharsets.UTF_8) } - -// TODO: migrate legacy java-style implementation to newer utils like RecorderCallback -private fun Any.wait(timeout: Long) = (this as Object).wait(timeout) -private fun Any.notify() = (this as Object).notify() \ No newline at end of file