1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.netpolicy;
18 
19 import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
20 
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.fail;
24 
25 import com.android.ddmlib.Log;
26 import com.android.tradefed.config.Option;
27 import com.android.tradefed.device.DeviceNotAvailableException;
28 import com.android.tradefed.invoker.TestInformation;
29 import com.android.tradefed.targetprep.BuildError;
30 import com.android.tradefed.targetprep.TargetSetupError;
31 import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
32 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
33 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
34 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
35 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
36 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
37 import com.android.tradefed.util.RunUtil;
38 
39 import org.junit.runner.RunWith;
40 
41 import java.util.Map;
42 
43 @RunWith(DeviceJUnit4ClassRunner.class)
44 abstract class HostsideNetworkPolicyTestCase extends BaseHostJUnit4Test {
45     protected static final boolean DEBUG = false;
46     protected static final String TAG = "HostsideNetworkPolicyTests";
47     protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
48     protected static final String TEST_APK = "CtsHostsideNetworkPolicyTestsApp.apk";
49     protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
50     protected static final String TEST_APP2_APK = "CtsHostsideNetworkPolicyTestsApp2.apk";
51 
52     @Option(name = "custom-url", importance = Option.Importance.IF_UNSET,
53             description = "A custom url to use for testing network connections")
54     protected String mCustomUrl;
55 
56     @BeforeClassWithInfo
setUpOnceBase(TestInformation testInfo)57     public static void setUpOnceBase(TestInformation testInfo) throws Exception {
58         uninstallPackage(testInfo, TEST_PKG, false);
59         installPackage(testInfo, TEST_APK);
60     }
61 
62     @AfterClassWithInfo
tearDownOnceBase(TestInformation testInfo)63     public static void tearDownOnceBase(TestInformation testInfo)
64             throws DeviceNotAvailableException {
65         uninstallPackage(testInfo, TEST_PKG, true);
66     }
67 
68     // Custom static method to install the specified package, this is used to bypass auto-cleanup
69     // per test in BaseHostJUnit4.
installPackage(TestInformation testInfo, String apk)70     protected static void installPackage(TestInformation testInfo, String apk)
71             throws DeviceNotAvailableException, TargetSetupError {
72         assertNotNull(testInfo);
73         final int userId = testInfo.getDevice().getCurrentUser();
74         final SuiteApkInstaller installer = new SuiteApkInstaller();
75         // Force the apk clean up
76         installer.setCleanApk(true);
77         installer.addTestFileName(apk);
78         installer.setUserId(userId);
79         installer.setShouldGrantPermission(true);
80         installer.addInstallArg("-t");
81         try {
82             installer.setUp(testInfo);
83         } catch (BuildError e) {
84             throw new TargetSetupError(
85                     e.getMessage(), e, testInfo.getDevice().getDeviceDescriptor(), e.getErrorId());
86         }
87     }
88 
installPackage(String apk)89     protected void installPackage(String apk) throws DeviceNotAvailableException, TargetSetupError {
90         installPackage(getTestInformation(), apk);
91     }
92 
uninstallPackage(TestInformation testInfo, String packageName, boolean shouldSucceed)93     protected static void uninstallPackage(TestInformation testInfo, String packageName,
94             boolean shouldSucceed)
95             throws DeviceNotAvailableException {
96         assertNotNull(testInfo);
97         final String result = testInfo.getDevice().uninstallPackage(packageName);
98         if (shouldSucceed) {
99             assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
100         }
101     }
102 
uninstallPackage(String packageName, boolean shouldSucceed)103     protected void uninstallPackage(String packageName,
104             boolean shouldSucceed)
105             throws DeviceNotAvailableException {
106         uninstallPackage(getTestInformation(), packageName, shouldSucceed);
107     }
108 
assertPackageUninstalled(String packageName)109     protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException {
110         final String command = "cmd package list packages " + packageName;
111         final int max_tries = 5;
112         for (int i = 1; i <= max_tries; i++) {
113             final String result = runCommand(command);
114             if (result.trim().isEmpty()) {
115                 return;
116             }
117             // 'list packages' filters by substring, so we need to iterate with the results
118             // and check one by one, otherwise 'com.android.cts.netpolicy.hostside' could return
119             // 'com.android.cts.netpolicy.hostside.app2'
120             boolean found = false;
121             for (String line : result.split("[\\r\\n]+")) {
122                 if (line.endsWith(packageName)) {
123                     found = true;
124                     break;
125                 }
126             }
127             if (!found) {
128                 return;
129             }
130             Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
131                     + "); sleeping 1s before polling again");
132             RunUtil.getDefault().sleep(1000);
133         }
134         fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
135     }
136 
getUid(String packageName)137     protected int getUid(String packageName) throws DeviceNotAvailableException {
138         final int currentUser = getDevice().getCurrentUser();
139         final String uidLines = runCommand(
140                 "cmd package list packages -U --user " + currentUser + " " + packageName);
141         for (String uidLine : uidLines.split("\n")) {
142             if (uidLine.startsWith("package:" + packageName + " uid:")) {
143                 final String[] uidLineParts = uidLine.split(":");
144                 // 3rd entry is package uid
145                 return Integer.parseInt(uidLineParts[2].trim());
146             }
147         }
148         throw new IllegalStateException("Failed to find the test app on the device; pkg="
149                 + packageName + ", u=" + currentUser);
150     }
151 
runDeviceTestsWithCustomOptions(String packageName, String className)152     protected boolean runDeviceTestsWithCustomOptions(String packageName, String className)
153             throws DeviceNotAvailableException {
154         return runDeviceTestsWithCustomOptions(packageName, className, null);
155     }
156 
runDeviceTestsWithCustomOptions(String packageName, String className, String methodName)157     protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
158             String methodName) throws DeviceNotAvailableException {
159         return runDeviceTestsWithCustomOptions(packageName, className, methodName, null);
160     }
161 
runDeviceTestsWithCustomOptions(String packageName, String className, String methodName, Map<String, String> testArgs)162     protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
163             String methodName, Map<String, String> testArgs) throws DeviceNotAvailableException {
164         final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
165                 .setTestClassName(className)
166                 .setTestMethodName(methodName);
167 
168         // Currently there is only one custom option that the test exposes.
169         if (mCustomUrl != null) {
170             deviceTestRunOptions.addInstrumentationArg(ARG_CONNECTION_CHECK_CUSTOM_URL, mCustomUrl);
171         }
172         // Pass over any test specific arguments.
173         if (testArgs != null) {
174             for (Map.Entry<String, String> arg : testArgs.entrySet()) {
175                 deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
176             }
177         }
178         return runDeviceTests(deviceTestRunOptions);
179     }
180 
runCommand(String command)181     protected String runCommand(String command) throws DeviceNotAvailableException {
182         Log.d(TAG, "Command: '" + command + "'");
183         final String output = getDevice().executeShellCommand(command);
184         if (DEBUG) Log.v(TAG, "Output: " + output.trim());
185         return output;
186     }
187 }
188