Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2017 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 android.media.cts;
     18 
     19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
     20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
     21 import com.android.ddmlib.testrunner.TestResult.TestStatus;
     22 import com.android.tradefed.build.IBuildInfo;
     23 import com.android.tradefed.device.DeviceNotAvailableException;
     24 import com.android.tradefed.log.LogUtil.CLog;
     25 import com.android.tradefed.result.CollectingTestListener;
     26 import com.android.tradefed.result.TestDescription;
     27 import com.android.tradefed.result.TestResult;
     28 import com.android.tradefed.result.TestRunResult;
     29 import com.android.tradefed.testtype.DeviceTestCase;
     30 import com.android.tradefed.testtype.IBuildReceiver;
     31 
     32 import java.io.FileNotFoundException;
     33 import java.util.ArrayList;
     34 import java.util.HashSet;
     35 import java.util.List;
     36 import java.util.Map;
     37 import java.util.Set;
     38 import java.util.concurrent.TimeUnit;
     39 
     40 import javax.annotation.Nonnull;
     41 import javax.annotation.Nullable;
     42 
     43 /**
     44  * Base class for host-side tests for multi-user aware media APIs.
     45  */
     46 public class BaseMultiUserTest extends DeviceTestCase implements IBuildReceiver {
     47     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
     48 
     49     /**
     50      * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
     51      * command output from the device. At any time, if the shell command does not output anything
     52      * for a period longer than the defined timeout the Tradefed run terminates.
     53      */
     54     private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
     55 
     56     /**
     57      * Instrumentation test runner argument key used for individual test timeout
     58      **/
     59     protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
     60 
     61     /**
     62      * Sets timeout (in milliseconds) that will be applied to each test. In the
     63      * event of a test timeout it will log the results and proceed with executing the next test.
     64      */
     65     private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
     66     private static final String SETTINGS_PACKAGE_VERIFIER_NAMESPACE = "global";
     67     private static final String SETTINGS_PACKAGE_VERIFIER_NAME = "package_verifier_enable";
     68 
     69     /**
     70      * User ID for all users.
     71      * The value is from the UserHandle class.
     72      */
     73     protected static final int USER_ALL = -1;
     74 
     75     /**
     76      * User ID for the system user.
     77      * The value is from the UserHandle class.
     78      */
     79     protected static final int USER_SYSTEM = 0;
     80 
     81     private IBuildInfo mCtsBuild;
     82     private String mPackageVerifier;
     83 
     84     private Set<String> mExistingPackages;
     85     private List<Integer> mExistingUsers;
     86     private HashSet<String> mAvailableFeatures;
     87 
     88     @Override
     89     protected void setUp() throws Exception {
     90         super.setUp();
     91         // Ensure that build has been set before test is run.
     92         assertNotNull(mCtsBuild);
     93         mExistingPackages = getDevice().getInstalledPackageNames();
     94 
     95         // Disable the package verifier to avoid the dialog when installing an app
     96         mPackageVerifier =
     97                 getSettings(
     98                         SETTINGS_PACKAGE_VERIFIER_NAMESPACE,
     99                         SETTINGS_PACKAGE_VERIFIER_NAME,
    100                         USER_ALL);
    101         putSettings(
    102                 SETTINGS_PACKAGE_VERIFIER_NAMESPACE,
    103                 SETTINGS_PACKAGE_VERIFIER_NAME,
    104                 "0",
    105                 USER_ALL);
    106 
    107         mExistingUsers = new ArrayList();
    108         int primaryUserId = getDevice().getPrimaryUserId();
    109         mExistingUsers.add(primaryUserId);
    110         mExistingUsers.add(USER_SYSTEM);
    111 
    112         executeShellCommand("am switch-user " + primaryUserId);
    113         executeShellCommand("wm dismiss-keyguard");
    114     }
    115 
    116     @Override
    117     protected void tearDown() throws Exception {
    118         // Reset the package verifier setting to its original value.
    119         putSettings(
    120                 SETTINGS_PACKAGE_VERIFIER_NAMESPACE,
    121                 SETTINGS_PACKAGE_VERIFIER_NAME,
    122                 mPackageVerifier,
    123                 USER_ALL);
    124 
    125         // Remove users created during the test.
    126         for (int userId : getDevice().listUsers()) {
    127             if (!mExistingUsers.contains(userId)) {
    128                 removeUser(userId);
    129             }
    130         }
    131         // Remove packages installed during the test.
    132         for (String packageName : getDevice().getUninstallablePackageNames()) {
    133             if (mExistingPackages.contains(packageName)) {
    134                 continue;
    135             }
    136             CLog.d("Removing leftover package: " + packageName);
    137             getDevice().uninstallPackage(packageName);
    138         }
    139         super.tearDown();
    140     }
    141 
    142     @Override
    143     public void setBuild(IBuildInfo buildInfo) {
    144         mCtsBuild = buildInfo;
    145     }
    146 
    147     /**
    148      * Installs the app as if the user of the ID {@param userId} has installed the app.
    149      *
    150      * @param appFileName file name of the app.
    151      * @param userId user ID to install the app against.
    152      */
    153     protected void installAppAsUser(String appFileName, int userId, boolean asInstantApp)
    154             throws FileNotFoundException, DeviceNotAvailableException {
    155         CLog.d("Installing app " + appFileName + " for user " + userId);
    156         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
    157         String result = getDevice().installPackageForUser(
    158                 buildHelper.getTestFile(appFileName),
    159                 true,
    160                 true,
    161                 userId,
    162                 "-t",
    163                 asInstantApp ? "--instant" : "");
    164         assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
    165                 result);
    166     }
    167 
    168     /**
    169      * Excutes shell command and returns the result.
    170      *
    171      * @param command command to run.
    172      * @return result from the command. If the result was {@code null}, empty string ("") will be
    173      *    returned instead. Otherwise, trimmed result will be returned.
    174      */
    175     protected @Nonnull String executeShellCommand(final String command) throws Exception {
    176         CLog.d("Starting command " + command);
    177         String commandOutput = getDevice().executeShellCommand(command);
    178         CLog.d("Output for command " + command + ": " + commandOutput);
    179         return commandOutput != null ? commandOutput.trim() : "";
    180     }
    181 
    182     private int createAndStartUser(String extraParam) throws Exception {
    183         String command = "pm create-user" + extraParam + " TestUser_" + System.currentTimeMillis();
    184         String commandOutput = executeShellCommand(command);
    185 
    186         String[] tokens = commandOutput.split("\\s+");
    187         assertTrue(tokens.length > 0);
    188         assertEquals("Success:", tokens[0]);
    189         int userId = Integer.parseInt(tokens[tokens.length-1]);
    190 
    191         // Start user for MediaSessionService to notice the created user.
    192         getDevice().startUser(userId);
    193         return userId;
    194     }
    195 
    196     /**
    197      * Creates and starts a new user.
    198      */
    199     protected int createAndStartUser() throws Exception {
    200         return createAndStartUser("");
    201     }
    202 
    203     /**
    204      * Creates and starts a restricted profile for the {@param parentUserId}.
    205      *
    206      * @param parentUserId parent user id.
    207      */
    208     protected int createAndStartRestrictedProfile(int parentUserId) throws Exception {
    209         return createAndStartUser(" --profileOf " + parentUserId + " --restricted");
    210     }
    211 
    212     /**
    213      * Creates and starts a managed profile for the {@param parentUserId}.
    214      *
    215      * @param parentUserId parent user id.
    216      */
    217     protected int createAndStartManagedProfile(int parentUserId) throws Exception {
    218         return createAndStartUser(" --profileOf " + parentUserId + " --managed");
    219     }
    220 
    221     /**
    222      * Removes the user that is created during the test.
    223      * <p>It will be no-op if the user cannot be removed or doesn't exist.
    224      *
    225      * @param userId user ID to remove.
    226      */
    227     protected void removeUser(int userId) throws Exception  {
    228         if (getDevice().listUsers().contains(userId) && userId != USER_SYSTEM
    229                 && !mExistingUsers.contains(userId)) {
    230             // Don't log output, as tests sometimes set no debug user restriction, which
    231             // causes this to fail, we should still continue and remove the user.
    232             String stopUserCommand = "am stop-user -w -f " + userId;
    233             CLog.d("Stopping and removing user " + userId);
    234             getDevice().executeShellCommand(stopUserCommand);
    235             assertTrue("Couldn't remove user", getDevice().removeUser(userId));
    236         }
    237     }
    238 
    239     /**
    240      * Runs tests on the device as if it's {@param userId}.
    241      *
    242      * @param pkgName test package file name that contains the {@link AndroidTestCase}
    243      * @param testClassName Class name to test within the test package. Can be {@code null} if you
    244      *    want to run all test classes in the package.
    245      * @param testMethodName Method name to test within the test class. Can be {@code null} if you
    246      *    want to run all test methods in the class. Will be ignored if {@param testClassName} is
    247      *    {@code null}.
    248      * @param userId user ID to run the tests as.
    249      */
    250     protected void runDeviceTestsAsUser(
    251             String pkgName, @Nullable String testClassName,
    252             @Nullable String testMethodName, int userId) throws DeviceNotAvailableException {
    253         if (testClassName != null && testClassName.startsWith(".")) {
    254             testClassName = pkgName + testClassName;
    255         }
    256 
    257         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
    258                 pkgName, RUNNER, getDevice().getIDevice());
    259         testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    260         testRunner.addInstrumentationArg(
    261                 TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
    262         if (testClassName != null && testMethodName != null) {
    263             testRunner.setMethodName(testClassName, testMethodName);
    264         } else if (testClassName != null) {
    265             testRunner.setClassName(testClassName);
    266         }
    267 
    268         CollectingTestListener listener = new CollectingTestListener();
    269         assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
    270 
    271         final TestRunResult result = listener.getCurrentRunResults();
    272         if (result.isRunFailure()) {
    273             throw new AssertionError("Failed to successfully run device tests for "
    274                     + result.getName() + ": " + result.getRunFailureMessage());
    275         }
    276         if (result.getNumTests() == 0) {
    277             throw new AssertionError("No tests were run on the device");
    278         }
    279 
    280         if (result.hasFailedTests()) {
    281             // Build a meaningful error message
    282             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
    283             for (Map.Entry<TestDescription, TestResult> resultEntry :
    284                     result.getTestResults().entrySet()) {
    285                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
    286                     errorBuilder.append(resultEntry.getKey().toString());
    287                     errorBuilder.append(":\n");
    288                     errorBuilder.append(resultEntry.getValue().getStackTrace());
    289                 }
    290             }
    291             throw new AssertionError(errorBuilder.toString());
    292         }
    293     }
    294 
    295     /**
    296      * Checks whether it is possible to create the desired number of users.
    297      */
    298     protected boolean canCreateAdditionalUsers(int numberOfUsers)
    299             throws DeviceNotAvailableException {
    300         return getDevice().listUsers().size() + numberOfUsers <=
    301                 getDevice().getMaxNumberOfUsersSupported();
    302     }
    303 
    304     /**
    305      * Gets the system setting as a string from the system settings provider for the user.
    306      *
    307      * @param namespace namespace of the setting.
    308      * @param name name of the setting.
    309      * @param userId user ID to query the setting. Can be {@link #USER_ALL}.
    310      * @return value of the system setting provider with the given namespace and name.
    311      *    {@code null}, empty string, or "null" will be returned to the empty string ("") instead.
    312      */
    313     protected @Nonnull String getSettings(@Nonnull String namespace, @Nonnull String name,
    314             int userId) throws Exception {
    315         String userFlag = (userId == USER_ALL) ? "" : " --user " + userId;
    316         String commandOutput = executeShellCommand(
    317                 "settings" + userFlag + " get " + namespace + " " + name);
    318         if (commandOutput == null || commandOutput.isEmpty() || commandOutput.equals("null")) {
    319             commandOutput = "";
    320         }
    321         return commandOutput;
    322     }
    323 
    324     /**
    325      * Puts the string to the system settings provider for the user.
    326      * <p>This deletes the setting for an empty {@param value} as 'settings put' doesn't allow
    327      * putting empty value.
    328      *
    329      * @param namespace namespace of the setting.
    330      * @param name name of the setting.
    331      * @param value value of the system setting provider with the given namespace and name.
    332      * @param userId user ID to set the setting. Can be {@link #USER_ALL}.
    333      */
    334     protected void putSettings(@Nonnull String namespace, @Nonnull String name,
    335             @Nullable String value, int userId) throws Exception {
    336         if (value == null || value.isEmpty()) {
    337             // Delete the setting if the value is null or empty as 'settings put' doesn't accept
    338             // them.
    339             // Ignore userId here because 'settings delete' doesn't support it.
    340             executeShellCommand("settings delete " + namespace + " " + name);
    341         } else {
    342             String userFlag = (userId == USER_ALL) ? "" : " --user " + userId;
    343             executeShellCommand("settings" + userFlag + " put " + namespace + " " + name
    344                     + " " + value);
    345         }
    346     }
    347 
    348     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
    349         if (mAvailableFeatures == null) {
    350             // TODO: Move this logic to ITestDevice.
    351             String command = "pm list features";
    352             String commandOutput = getDevice().executeShellCommand(command);
    353             CLog.i("Output for command " + command + ": " + commandOutput);
    354 
    355             // Extract the id of the new user.
    356             mAvailableFeatures = new HashSet<>();
    357             for (String feature : commandOutput.split("\\s+")) {
    358                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
    359                 String[] tokens = feature.split(":");
    360                 assertTrue(
    361                         "\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
    362                         tokens.length > 1);
    363                 assertEquals(feature, "feature", tokens[0]);
    364                 mAvailableFeatures.add(tokens[1]);
    365             }
    366         }
    367         boolean result = mAvailableFeatures.contains(requiredFeature);
    368         return result;
    369     }
    370 }
    371