From d025c562f0424381f6657aefdc6b33dbbdc2128a Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Sun, 28 Feb 2021 17:32:30 +0900 Subject: [PATCH 1/4] Make TestConnectivityManager usable by other tethering tests. Currently, this class is a static inner class of UpstreamNetworkMonitorTest. Extract it to its own top-level class so it can be used by other tests. Bug: 173068192 Test: atest TetheringTests Change-Id: I6bdb090a99781ac2530b3924ac5c4cf78de315b0 --- .../tethering/TestConnectivityManager.java | 247 ++++++++++++++++++ .../tethering/UpstreamNetworkMonitorTest.java | 199 +------------- 2 files changed, 250 insertions(+), 196 deletions(-) create mode 100644 Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java new file mode 100644 index 0000000000..3a6350c026 --- /dev/null +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2021 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 com.android.networkstack.tethering; + +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.Handler; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Simulates upstream switching and sending NetworkCallbacks and CONNECTIVITY_ACTION broadcasts. + * + * TODO: this duplicates a fair amount of code from ConnectivityManager and ConnectivityService. + * Consider using a ConnectivityService object instead, as used in ConnectivityServiceTest. + * + * Things to consider: + * - ConnectivityService uses a real handler for realism, and these test use TestLooper (or even + * invoke callbacks directly inline) for determinism. Using a real ConnectivityService would + * require adding dispatchAll() calls and migrating to handlers. + * - ConnectivityService does not provide a way to order CONNECTIVITY_ACTION before or after the + * NetworkCallbacks for the same network change. That ability is useful because the upstream + * selection code in Tethering is vulnerable to race conditions, due to its reliance on multiple + * separate NetworkCallbacks and BroadcastReceivers, each of which trigger different types of + * updates. If/when the upstream selection code is refactored to a more level-triggered model + * (e.g., with an idempotent function that takes into account all state every time any part of + * that state changes), this may become less important or unnecessary. + */ +public class TestConnectivityManager extends ConnectivityManager { + public Map allCallbacks = new HashMap<>(); + public Set trackingDefault = new HashSet<>(); + public TestNetworkAgent defaultNetwork = null; + public Map listening = new HashMap<>(); + public Map requested = new HashMap<>(); + public Map legacyTypeMap = new HashMap<>(); + + private final NetworkRequest mDefaultRequest; + private int mNetworkId = 100; + + public TestConnectivityManager(Context ctx, IConnectivityManager svc, + NetworkRequest defaultRequest) { + super(ctx, svc); + mDefaultRequest = defaultRequest; + } + + boolean hasNoCallbacks() { + return allCallbacks.isEmpty() + && trackingDefault.isEmpty() + && listening.isEmpty() + && requested.isEmpty() + && legacyTypeMap.isEmpty(); + } + + boolean onlyHasDefaultCallbacks() { + return (allCallbacks.size() == 1) + && (trackingDefault.size() == 1) + && listening.isEmpty() + && requested.isEmpty() + && legacyTypeMap.isEmpty(); + } + + boolean isListeningForAll() { + final NetworkCapabilities empty = new NetworkCapabilities(); + empty.clearAll(); + + for (NetworkRequest req : listening.values()) { + if (req.networkCapabilities.equalRequestableCapabilities(empty)) { + return true; + } + } + return false; + } + + int getNetworkId() { + return ++mNetworkId; + } + + void makeDefaultNetwork(TestNetworkAgent agent) { + if (Objects.equals(defaultNetwork, agent)) return; + + final TestNetworkAgent formerDefault = defaultNetwork; + defaultNetwork = agent; + + for (NetworkCallback cb : trackingDefault) { + if (defaultNetwork != null) { + cb.onAvailable(defaultNetwork.networkId); + cb.onCapabilitiesChanged( + defaultNetwork.networkId, defaultNetwork.networkCapabilities); + cb.onLinkPropertiesChanged( + defaultNetwork.networkId, defaultNetwork.linkProperties); + } + } + } + + @Override + public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { + assertFalse(allCallbacks.containsKey(cb)); + allCallbacks.put(cb, h); + if (mDefaultRequest.equals(req)) { + assertFalse(trackingDefault.contains(cb)); + trackingDefault.add(cb); + } else { + assertFalse(requested.containsKey(cb)); + requested.put(cb, req); + } + } + + @Override + public void requestNetwork(NetworkRequest req, NetworkCallback cb) { + fail("Should never be called."); + } + + @Override + public void requestNetwork(NetworkRequest req, + int timeoutMs, int legacyType, Handler h, NetworkCallback cb) { + assertFalse(allCallbacks.containsKey(cb)); + allCallbacks.put(cb, h); + assertFalse(requested.containsKey(cb)); + requested.put(cb, req); + assertFalse(legacyTypeMap.containsKey(cb)); + if (legacyType != ConnectivityManager.TYPE_NONE) { + legacyTypeMap.put(cb, legacyType); + } + } + + @Override + public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) { + assertFalse(allCallbacks.containsKey(cb)); + allCallbacks.put(cb, h); + assertFalse(listening.containsKey(cb)); + listening.put(cb, req); + } + + @Override + public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) { + fail("Should never be called."); + } + + @Override + public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) { + fail("Should never be called."); + } + + @Override + public void registerDefaultNetworkCallback(NetworkCallback cb) { + fail("Should never be called."); + } + + @Override + public void unregisterNetworkCallback(NetworkCallback cb) { + if (trackingDefault.contains(cb)) { + trackingDefault.remove(cb); + } else if (listening.containsKey(cb)) { + listening.remove(cb); + } else if (requested.containsKey(cb)) { + requested.remove(cb); + legacyTypeMap.remove(cb); + } else { + fail("Unexpected callback removed"); + } + allCallbacks.remove(cb); + + assertFalse(allCallbacks.containsKey(cb)); + assertFalse(trackingDefault.contains(cb)); + assertFalse(listening.containsKey(cb)); + assertFalse(requested.containsKey(cb)); + } + + public static class TestNetworkAgent { + public final TestConnectivityManager cm; + public final Network networkId; + public final int transportType; + public final NetworkCapabilities networkCapabilities; + public final LinkProperties linkProperties; + + public TestNetworkAgent(TestConnectivityManager cm, int transportType) { + this.cm = cm; + this.networkId = new Network(cm.getNetworkId()); + this.transportType = transportType; + networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addTransportType(transportType); + networkCapabilities.addCapability(NET_CAPABILITY_INTERNET); + linkProperties = new LinkProperties(); + } + + public void fakeConnect() { + for (NetworkCallback cb : cm.listening.keySet()) { + cb.onAvailable(networkId); + cb.onCapabilitiesChanged(networkId, copy(networkCapabilities)); + cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); + } + } + + public void fakeDisconnect() { + for (NetworkCallback cb : cm.listening.keySet()) { + cb.onLost(networkId); + } + } + + public void sendLinkProperties() { + for (NetworkCallback cb : cm.listening.keySet()) { + cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); + } + } + + @Override + public String toString() { + return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities); + } + } + + static NetworkCapabilities copy(NetworkCapabilities nc) { + return new NetworkCapabilities(nc); + } + + static LinkProperties copy(LinkProperties lp) { + return new LinkProperties(lp); + } +} diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java index 232588c7ee..e358f5a2cd 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java @@ -29,7 +29,6 @@ import static com.android.networkstack.tethering.UpstreamNetworkMonitor.TYPE_NON import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; @@ -48,7 +47,6 @@ import android.net.IConnectivityManager; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.util.SharedLog; @@ -60,6 +58,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent; import org.junit.After; import org.junit.Before; @@ -71,10 +70,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; -import java.util.Objects; import java.util.Set; @RunWith(AndroidJUnit4.class) @@ -106,7 +102,7 @@ public class UpstreamNetworkMonitorTest { when(mLog.forSubComponent(anyString())).thenReturn(mLog); when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); - mCM = spy(new TestConnectivityManager(mContext, mCS)); + mCM = spy(new TestConnectivityManager(mContext, mCS, sDefaultRequest)); mSM = new TestStateMachine(); mUNM = new UpstreamNetworkMonitor( (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE); @@ -567,187 +563,6 @@ public class UpstreamNetworkMonitorTest { return false; } - public static class TestConnectivityManager extends ConnectivityManager { - public Map allCallbacks = new HashMap<>(); - public Set trackingDefault = new HashSet<>(); - public TestNetworkAgent defaultNetwork = null; - public Map listening = new HashMap<>(); - public Map requested = new HashMap<>(); - public Map legacyTypeMap = new HashMap<>(); - - private int mNetworkId = 100; - - public TestConnectivityManager(Context ctx, IConnectivityManager svc) { - super(ctx, svc); - } - - boolean hasNoCallbacks() { - return allCallbacks.isEmpty() - && trackingDefault.isEmpty() - && listening.isEmpty() - && requested.isEmpty() - && legacyTypeMap.isEmpty(); - } - - boolean onlyHasDefaultCallbacks() { - return (allCallbacks.size() == 1) - && (trackingDefault.size() == 1) - && listening.isEmpty() - && requested.isEmpty() - && legacyTypeMap.isEmpty(); - } - - boolean isListeningForAll() { - final NetworkCapabilities empty = new NetworkCapabilities(); - empty.clearAll(); - - for (NetworkRequest req : listening.values()) { - if (req.networkCapabilities.equalRequestableCapabilities(empty)) { - return true; - } - } - return false; - } - - int getNetworkId() { - return ++mNetworkId; - } - - void makeDefaultNetwork(TestNetworkAgent agent) { - if (Objects.equals(defaultNetwork, agent)) return; - - final TestNetworkAgent formerDefault = defaultNetwork; - defaultNetwork = agent; - - for (NetworkCallback cb : trackingDefault) { - if (defaultNetwork != null) { - cb.onAvailable(defaultNetwork.networkId); - cb.onCapabilitiesChanged( - defaultNetwork.networkId, defaultNetwork.networkCapabilities); - cb.onLinkPropertiesChanged( - defaultNetwork.networkId, defaultNetwork.linkProperties); - } - } - } - - @Override - public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { - assertFalse(allCallbacks.containsKey(cb)); - allCallbacks.put(cb, h); - if (sDefaultRequest.equals(req)) { - assertFalse(trackingDefault.contains(cb)); - trackingDefault.add(cb); - } else { - assertFalse(requested.containsKey(cb)); - requested.put(cb, req); - } - } - - @Override - public void requestNetwork(NetworkRequest req, NetworkCallback cb) { - fail("Should never be called."); - } - - @Override - public void requestNetwork(NetworkRequest req, - int timeoutMs, int legacyType, Handler h, NetworkCallback cb) { - assertFalse(allCallbacks.containsKey(cb)); - allCallbacks.put(cb, h); - assertFalse(requested.containsKey(cb)); - requested.put(cb, req); - assertFalse(legacyTypeMap.containsKey(cb)); - if (legacyType != ConnectivityManager.TYPE_NONE) { - legacyTypeMap.put(cb, legacyType); - } - } - - @Override - public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) { - assertFalse(allCallbacks.containsKey(cb)); - allCallbacks.put(cb, h); - assertFalse(listening.containsKey(cb)); - listening.put(cb, req); - } - - @Override - public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) { - fail("Should never be called."); - } - - @Override - public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) { - fail("Should never be called."); - } - - @Override - public void registerDefaultNetworkCallback(NetworkCallback cb) { - fail("Should never be called."); - } - - @Override - public void unregisterNetworkCallback(NetworkCallback cb) { - if (trackingDefault.contains(cb)) { - trackingDefault.remove(cb); - } else if (listening.containsKey(cb)) { - listening.remove(cb); - } else if (requested.containsKey(cb)) { - requested.remove(cb); - legacyTypeMap.remove(cb); - } else { - fail("Unexpected callback removed"); - } - allCallbacks.remove(cb); - - assertFalse(allCallbacks.containsKey(cb)); - assertFalse(trackingDefault.contains(cb)); - assertFalse(listening.containsKey(cb)); - assertFalse(requested.containsKey(cb)); - } - } - - public static class TestNetworkAgent { - public final TestConnectivityManager cm; - public final Network networkId; - public final int transportType; - public final NetworkCapabilities networkCapabilities; - public final LinkProperties linkProperties; - - public TestNetworkAgent(TestConnectivityManager cm, int transportType) { - this.cm = cm; - this.networkId = new Network(cm.getNetworkId()); - this.transportType = transportType; - networkCapabilities = new NetworkCapabilities(); - networkCapabilities.addTransportType(transportType); - networkCapabilities.addCapability(NET_CAPABILITY_INTERNET); - linkProperties = new LinkProperties(); - } - - public void fakeConnect() { - for (NetworkCallback cb : cm.listening.keySet()) { - cb.onAvailable(networkId); - cb.onCapabilitiesChanged(networkId, copy(networkCapabilities)); - cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); - } - } - - public void fakeDisconnect() { - for (NetworkCallback cb : cm.listening.keySet()) { - cb.onLost(networkId); - } - } - - public void sendLinkProperties() { - for (NetworkCallback cb : cm.listening.keySet()) { - cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); - } - } - - @Override - public String toString() { - return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities); - } - } - public static class TestStateMachine extends StateMachine { public final ArrayList messages = new ArrayList<>(); private final State mLoggingState = new LoggingState(); @@ -775,14 +590,6 @@ public class UpstreamNetworkMonitorTest { } } - static NetworkCapabilities copy(NetworkCapabilities nc) { - return new NetworkCapabilities(nc); - } - - static LinkProperties copy(LinkProperties lp) { - return new LinkProperties(lp); - } - static void assertPrefixSet(Set prefixes, boolean expectation, String... expected) { final Set expectedSet = new HashSet<>(); Collections.addAll(expectedSet, expected); @@ -797,4 +604,4 @@ public class UpstreamNetworkMonitorTest { expectation, prefixes.contains(new IpPrefix(expectedPrefix))); } } -} +} \ No newline at end of file From bf69824479fbf95a653af22cee311031a6e23cfa Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Tue, 16 Feb 2021 14:14:06 +0900 Subject: [PATCH 2/4] Change TetheringTest's UpstreamNetworkMonitor from mock to spy. This allows future tests that want to exercise the interactions between Tethering and UNM to do so. Also verify what happens when UNM is initialized, and in setUp, capture the NetworkCallback it files to track the default network, so tests can send it NetworkCallbacks. (This callback is only ever filed once.) Test: test-only change Change-Id: Iff9b62120cced41cc61263bfd4fa34f575d0ac00 --- .../networkstack/tethering/TetheringTest.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index c5a43b72dd..57c8862ba9 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -64,6 +64,7 @@ import static com.android.testutils.TestPermissionUtil.runAsShell; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; @@ -72,6 +73,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -95,6 +97,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.EthernetManager; import android.net.EthernetManager.TetheredInterfaceCallback; import android.net.EthernetManager.TetheredInterfaceRequest; @@ -212,7 +215,6 @@ public class TetheringTest { @Mock private UsbManager mUsbManager; @Mock private WifiManager mWifiManager; @Mock private CarrierConfigManager mCarrierConfigManager; - @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor; @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator; @Mock private DadProxy mDadProxy; @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon; @@ -249,6 +251,8 @@ public class TetheringTest { private OffloadController mOffloadCtrl; private PrivateAddressCoordinator mPrivateAddressCoordinator; private SoftApCallback mSoftApCallback; + private UpstreamNetworkMonitor mUpstreamNetworkMonitor; + private NetworkCallback mDefaultNetworkCallback; private class TestContext extends BroadcastInterceptingContext { TestContext(Context base) { @@ -400,7 +404,10 @@ public class TetheringTest { @Override public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target, SharedLog log, int what) { + // Use a real object instead of a mock so that some tests can use a real UNM and some + // can use a mock. mUpstreamNetworkMonitorSM = target; + mUpstreamNetworkMonitor = spy(super.getUpstreamNetworkMonitor(ctx, target, log, what)); return mUpstreamNetworkMonitor; } @@ -581,6 +588,8 @@ public class TetheringTest { mTethering = makeTethering(); verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any()); verify(mNetd).registerUnsolicitedEventListener(any()); + verifyDefaultNetworkRequestFiled(); + final ArgumentCaptor phoneListenerCaptor = ArgumentCaptor.forClass(PhoneStateListener.class); verify(mTelephonyManager).listen(phoneListenerCaptor.capture(), @@ -617,8 +626,8 @@ public class TetheringTest { } private void initTetheringUpstream(UpstreamNetworkState upstreamState) { - when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState); - when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState); + doReturn(upstreamState).when(mUpstreamNetworkMonitor).getCurrentPreferredUpstream(); + doReturn(upstreamState).when(mUpstreamNetworkMonitor).selectPreferredUpstreamType(any()); } private Tethering makeTethering() { @@ -705,6 +714,19 @@ public class TetheringTest { mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } + private void verifyDefaultNetworkRequestFiled() { + ArgumentCaptor captor = ArgumentCaptor.forClass(NetworkCallback.class); + verify(mCm, times(1)).requestNetwork(eq(mNetworkRequest), + captor.capture(), any(Handler.class)); + mDefaultNetworkCallback = captor.getValue(); + assertNotNull(mDefaultNetworkCallback); + + // The default network request is only ever filed once. + verifyNoMoreInteractions(mCm); + mUpstreamNetworkMonitor.startTrackDefaultNetwork(mNetworkRequest, mEntitleMgr); + verifyNoMoreInteractions(mCm); + } + private void verifyInterfaceServingModeStarted(String ifname) throws Exception { verify(mNetd, times(1)).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); verify(mNetd, times(1)).tetherInterfaceAdd(ifname); From b424a2794ec8bda048bb1464c6328d29214662d3 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Sun, 28 Feb 2021 17:41:13 +0900 Subject: [PATCH 3/4] Support building different UpstreamNetworkState test objects. TetheringTest is only able to build UpstreamNetworkState objects for mobile Internet networks. Support building wifi and dun versions as well. Bug: 173068192 Test: atest TetheringTests Change-Id: Id46f1e5b65dbe04e84a5f56343821af260e2539e --- .../networkstack/tethering/TetheringTest.java | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 57c8862ba9..3d3931f3cc 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -25,6 +25,7 @@ import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; @@ -203,6 +204,10 @@ public class TetheringTest { private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; + private static final int CELLULAR_NETID = 100; + private static final int WIFI_NETID = 101; + private static final int DUN_NETID = 102; + private static final int DHCPSERVER_START_TIMEOUT_MS = 1000; @Mock private ApplicationInfo mApplicationInfo; @@ -487,15 +492,15 @@ public class TetheringTest { } } - private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4, - boolean withIPv6, boolean with464xlat) { + private static LinkProperties buildUpstreamLinkProperties(String interfaceName, + boolean withIPv4, boolean withIPv6, boolean with464xlat) { final LinkProperties prop = new LinkProperties(); - prop.setInterfaceName(TEST_MOBILE_IFNAME); + prop.setInterfaceName(interfaceName); if (withIPv4) { prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), InetAddresses.parseNumericAddress("10.0.0.1"), - TEST_MOBILE_IFNAME, RTN_UNICAST)); + interfaceName, RTN_UNICAST)); } if (withIPv6) { @@ -505,23 +510,40 @@ public class TetheringTest { NetworkConstants.RFC7421_PREFIX_LENGTH)); prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), InetAddresses.parseNumericAddress("2001:db8::1"), - TEST_MOBILE_IFNAME, RTN_UNICAST)); + interfaceName, RTN_UNICAST)); } if (with464xlat) { + final String clatInterface = "v4-" + interfaceName; final LinkProperties stackedLink = new LinkProperties(); - stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME); + stackedLink.setInterfaceName(clatInterface); stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), InetAddresses.parseNumericAddress("192.0.0.1"), - TEST_XLAT_MOBILE_IFNAME, RTN_UNICAST)); + clatInterface, RTN_UNICAST)); prop.addStackedLink(stackedLink); } + return prop; + } - final NetworkCapabilities capabilities = new NetworkCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - return new UpstreamNetworkState(prop, capabilities, new Network(100)); + private static NetworkCapabilities buildUpstreamCapabilities(int transport, int... otherCaps) { + // TODO: add NOT_VCN_MANAGED. + final NetworkCapabilities nc = new NetworkCapabilities() + .addTransportType(transport) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + for (int cap : otherCaps) { + nc.addCapability(cap); + } + return nc; + } + + private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4, + boolean withIPv6, boolean with464xlat) { + return new UpstreamNetworkState( + buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, withIPv4, withIPv6, with464xlat), + buildUpstreamCapabilities(TRANSPORT_CELLULAR), + new Network(CELLULAR_NETID)); } private static UpstreamNetworkState buildMobileIPv4UpstreamState() { @@ -540,6 +562,22 @@ public class TetheringTest { return buildMobileUpstreamState(false, true, true); } + private static UpstreamNetworkState buildWifiUpstreamState() { + return new UpstreamNetworkState( + buildUpstreamLinkProperties(TEST_WIFI_IFNAME, true /* IPv4 */, true /* IPv6 */, + false /* 464xlat */), + buildUpstreamCapabilities(TRANSPORT_WIFI), + new Network(WIFI_NETID)); + } + + private static UpstreamNetworkState buildDunUpstreamState() { + return new UpstreamNetworkState( + buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, true /* IPv4 */, true /* IPv6 */, + false /* 464xlat */), + buildUpstreamCapabilities(TRANSPORT_CELLULAR, NET_CAPABILITY_DUN), + new Network(DUN_NETID)); + } + // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and // after use. @BeforeClass @@ -1904,7 +1942,8 @@ public class TetheringTest { // Verify that onUpstreamCapabilitiesChanged won't be called if not current upstream network // capabilities changed. final UpstreamNetworkState upstreamState2 = new UpstreamNetworkState( - upstreamState.linkProperties, upstreamState.networkCapabilities, new Network(101)); + upstreamState.linkProperties, upstreamState.networkCapabilities, + new Network(WIFI_NETID)); stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2); verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any()); } From 66287d3551e59109a308584473a12737e9094d39 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Sun, 28 Feb 2021 18:01:35 +0900 Subject: [PATCH 4/4] Use TestConnectivityManager in TetheringTest. The changes required are: - Change all usages of when(mCm.method()).thenReturn(...) to doReturn(...).when(mCm).method() because spies must use the latter syntax. - In setDataSaverEnabled, set the mocked return value before sending the broadcast. Otherwise, the first time the method is called, the spy will attempt to send the broadcast, and will crash because it does not have permission to do so. This does not do anything useful yet, but it will be used in future CLs. Bug: 173068192 Test: atest TetheringTests Change-Id: I968bfa76ead25b2d45ed1c0e8ede32df81401579 --- .../networkstack/tethering/TetheringTest.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 3d3931f3cc..40b7c55f71 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -25,7 +25,9 @@ import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; +import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; @@ -97,11 +99,11 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.usb.UsbManager; -import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.EthernetManager; import android.net.EthernetManager.TetheredInterfaceCallback; import android.net.EthernetManager.TetheredInterfaceRequest; +import android.net.IConnectivityManager; import android.net.IIntResultListener; import android.net.INetd; import android.net.ITetheringEventCallback; @@ -227,8 +229,6 @@ public class TetheringTest { @Mock private IDhcpServer mDhcpServer; @Mock private INetd mNetd; @Mock private UserManager mUserManager; - @Mock private NetworkRequest mNetworkRequest; - @Mock private ConnectivityManager mCm; @Mock private EthernetManager mEm; @Mock private TetheringNotificationUpdater mNotificationUpdater; @Mock private BpfCoordinator mBpfCoordinator; @@ -257,6 +257,9 @@ public class TetheringTest { private PrivateAddressCoordinator mPrivateAddressCoordinator; private SoftApCallback mSoftApCallback; private UpstreamNetworkMonitor mUpstreamNetworkMonitor; + + private TestConnectivityManager mCm; + private NetworkRequest mNetworkRequest; private NetworkCallback mDefaultNetworkCallback; private class TestContext extends BroadcastInterceptingContext { @@ -623,6 +626,17 @@ public class TetheringTest { }; mServiceContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TETHER_STATE_CHANGED)); + + // TODO: add NOT_VCN_MANAGED here, but more importantly in the production code. + // TODO: even better, change TetheringDependencies.getDefaultNetworkRequest() to use + // registerSystemDefaultNetworkCallback() on S and above. + NetworkCapabilities defaultCaps = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET); + mNetworkRequest = new NetworkRequest(defaultCaps, TYPE_NONE, 1 /* requestId */, + NetworkRequest.Type.REQUEST); + mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class), + mNetworkRequest)); + mTethering = makeTethering(); verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any()); verify(mNetd).registerUnsolicitedEventListener(any()); @@ -1783,12 +1797,12 @@ public class TetheringTest { } private void setDataSaverEnabled(boolean enabled) { - final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED); - mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL); - final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED : RESTRICT_BACKGROUND_STATUS_DISABLED; - when(mCm.getRestrictBackgroundStatus()).thenReturn(status); + doReturn(status).when(mCm).getRestrictBackgroundStatus(); + + final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED); + mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL); mLooper.dispatchAll(); } @@ -2053,7 +2067,7 @@ public class TetheringTest { public void testHandleIpConflict() throws Exception { final Network wifiNetwork = new Network(200); final Network[] allNetworks = { wifiNetwork }; - when(mCm.getAllNetworks()).thenReturn(allNetworks); + doReturn(allNetworks).when(mCm).getAllNetworks(); runUsbTethering(null); final ArgumentCaptor ifaceConfigCaptor = ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); @@ -2080,7 +2094,7 @@ public class TetheringTest { final Network btNetwork = new Network(201); final Network mobileNetwork = new Network(202); final Network[] allNetworks = { wifiNetwork, btNetwork, mobileNetwork }; - when(mCm.getAllNetworks()).thenReturn(allNetworks); + doReturn(allNetworks).when(mCm).getAllNetworks(); runUsbTethering(null); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks( any(), any());