blob: 32613f63ce9f71478126d45e99504fa3cf7855c5 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package arc
import (
"context"
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"chromiumos/tast/common/testexec"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/arc/playstore"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/cryptohome"
"chromiumos/tast/local/upstart"
"chromiumos/tast/testing"
)
const (
homeDataNameNycX86 = "data_migration_nyc_x86_64"
homeDataNamePiX86 = "data_migration_pi_x86_64"
homeDataNamePiArm = "data_migration_pi_arm64"
)
func init() {
testing.AddTest(&testing.Test{
Func: DataMigration,
Desc: "Boots ARC with /data created on the previous version of ARC and verifies Play Store can install an app",
Contacts: []string{"niwa@google.com", "arc-storage@google.com"},
Attr: []string{"group:mainline", "informational"},
// "no_qemu" is added for excluding betty from the target board list.
// TODO(b/179636279): Remove "no_qemu" after making the test pass on betty.
SoftwareDeps: []string{"chrome", "no_qemu"},
Timeout: 10 * time.Minute,
VarDeps: []string{"arc.DataMigration.username", "arc.DataMigration.password"},
Params: []testing.Param{{
// Launch ARC P with /data created on ARC N (for x86).
Name: "n_to_p_x86",
Val: homeDataNameNycX86,
ExtraData: []string{homeDataNameNycX86},
ExtraSoftwareDeps: []string{"android_p", "amd64"},
}, {
// Launch ARC R with /data created on ARC P (for x86).
Name: "p_to_r_x86",
Val: homeDataNamePiX86,
ExtraData: []string{homeDataNamePiX86},
ExtraSoftwareDeps: []string{"android_vm", "amd64"},
}, {
// Launch ARC R with /data created on ARC P (for arm).
Name: "p_to_r_arm",
Val: homeDataNamePiArm,
ExtraData: []string{homeDataNamePiArm},
ExtraSoftwareDeps: []string{"android_vm", "arm"},
}},
})
}
// DataMigration checks regressions for the following bugs:
// b/173835269 Can't download or install some apps after P->R data migration.
// b/183690158 vold hangs while processing fixupAllAppDirs() if there are thousands of files to fix.
// (Home data data_migration_pi_* contains 5000 dirs under
// /sdcard/Android/data/com.android.vending/files/ for reproducing this bug.)
// b/190293594 GMSCore for Pi is picked up on ARC R after P->R upgrade.
func DataMigration(ctx context.Context, s *testing.State) {
const (
// One of the apps reported by b/173835269.
appToInstall = "com.roblox.client"
)
username := s.RequiredVar("arc.DataMigration.username")
password := s.RequiredVar("arc.DataMigration.password")
homeDataPath := s.DataPath(s.Param().(string))
// Ensure to sign out before executing mountVaultWithArchivedHomeData().
if err := upstart.RestartJob(ctx, "ui"); err != nil {
s.Fatal("Failed to sign out: ", err)
}
// Use a shortened context for test operations to reserve time for cleanup.
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 30*time.Second)
defer cancel()
// Unarchive the home data under vault before signing in.
if err := mountVaultWithArchivedHomeData(ctx, homeDataPath, username, password); err != nil {
s.Fatal("Failed to mount home with archived data: ", err)
}
defer func() {
cryptohome.UnmountVault(cleanupCtx, username)
cryptohome.RemoveVault(cleanupCtx, username)
}()
args := append(arc.DisableSyncFlags(), "--disable-arc-data-wipe")
cr, err := chrome.New(ctx,
chrome.GAIALogin(chrome.Creds{User: username, Pass: password}),
chrome.ARCSupported(), chrome.KeepState(), chrome.ExtraArgs(args...))
if err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
defer cr.Close(cleanupCtx)
a, err := arc.New(ctx, s.OutDir())
if err != nil {
s.Fatal("Failed to start ARC: ", err)
}
defer a.Close(ctx)
systemSdkVersion, err := checkSdkVersionsInPackagesXML(ctx, username)
if err != nil {
s.Fatal("Failed to check SDK version in packages.xml: ", err)
}
// Regression check for b/190293594.
if err := checkGmsCoreVersion(ctx, a, systemSdkVersion); err != nil {
// Log error and continue testing.
s.Error("Failed to check GMSCore version: ", err)
}
d, err := a.NewUIDevice(ctx)
if err != nil {
s.Fatal("Failed initializing UI Automator: ", err)
}
defer d.Close(cleanupCtx)
// Regression check for b/173835269.
s.Log("Installing app " + appToInstall)
if err := playstore.InstallApp(ctx, a, d, appToInstall, -1); err != nil {
s.Error("Failed to install app: ", err)
}
}
func mountVaultWithArchivedHomeData(ctx context.Context, homeDataPath, username, password string) error {
// Unmount and mount vault for the user.
if err := cryptohome.UnmountVault(ctx, username); err != nil {
return err
}
if err := cryptohome.RemoveVault(ctx, username); err != nil {
return err
}
if err := cryptohome.CreateVault(ctx, username, password); err != nil {
return err
}
success := false
defer func() {
if !success {
cryptohome.UnmountVault(ctx, username)
cryptohome.RemoveVault(ctx, username)
}
}()
vaultPath, err := cryptohome.MountedVaultPath(ctx, username)
if err != nil {
return err
}
testing.ContextLogf(ctx, "Unarchiving home data %q under %q", homeDataPath, vaultPath)
if err := testexec.CommandContext(
ctx, "tar", "--xattrs", "--selinux", "-C", vaultPath, "-xjf", homeDataPath).Run(testexec.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to unarchive home data under vault")
}
success = true
return nil
}
// checkSdkVersionsInPackagesXML checks if system SDK version is higher than data SDK version and
// returns system SDK version.
func checkSdkVersionsInPackagesXML(ctx context.Context, username string) (int, error) {
const (
packagesXMLPath = "/data/system/packages.xml"
)
// SDK version of ARC running currently.
systemVersionRegexp := regexp.MustCompile(`\<version sdkVersion="(\d+)"`)
systemVersion := 0
// SDK version of ARC /data.
dataVersionRegexp := regexp.MustCompile(`\<version volumeUuid="\w+" sdkVersion="(\d+)"`)
dataVersion := 0
testing.ContextLogf(ctx, "Checking SDK versions in %s", packagesXMLPath)
rootCryptDir, err := cryptohome.SystemPath(username)
if err != nil {
return 0, errors.Wrap(err, "failed to get the cryptohome directory for the user")
}
// /home/root/<hash>/android-data/data/system/packages.xml
b, err := ioutil.ReadFile(filepath.Join(rootCryptDir, "android-data", packagesXMLPath))
if err != nil {
return 0, errors.Wrap(err, "failed to open packages.xml")
}
for _, l := range strings.Split(string(b), "\n") {
m := systemVersionRegexp.FindStringSubmatch(l)
if m != nil {
systemVersion, _ = strconv.Atoi(m[1])
}
m = dataVersionRegexp.FindStringSubmatch(l)
if m != nil {
dataVersion, _ = strconv.Atoi(m[1])
}
if systemVersion > 0 && dataVersion > 0 {
break
}
}
foundVersionsText := fmt.Sprintf("Found system SDK version: %d, data SDK verson: %d",
systemVersion, dataVersion)
testing.ContextLog(ctx, foundVersionsText)
if systemVersion <= 0 || dataVersion <= 0 {
return 0, errors.Wrapf(err, "failed to get system SDK version or data SDK version in packages.xml (%s)",
foundVersionsText)
}
if systemVersion <= dataVersion {
return 0, errors.Wrapf(err, "system SDK version should be higher than data SDK version (%s)",
foundVersionsText)
}
return systemVersion, nil
}
// checkGmsCoreVersion checks if ARC is using an expected version of GMSCore.
func checkGmsCoreVersion(ctx context.Context, a *arc.ARC, systemSdkVersion int) error {
// Regexp for matching a GMSCore version string in logcat.
// e.g. "com.google.android.gms@212013032@21.20.13 (100800-374639054)"
gmscoreVersionRegexp := regexp.MustCompile(
`com\.google\.android\.gms@\d+@\d+\.\d+\.\d+ \((\d{6})-\d+\)`)
out, err := a.Command(ctx, "logcat", "-d").Output(testexec.DumpLogOnError)
if err != nil {
return errors.Wrap(err, "failed to run logcat")
}
var fullVersionString string
var versionCode int
for _, line := range strings.Split(string(out), "\n") {
m := gmscoreVersionRegexp.FindStringSubmatch(line)
if len(m) == 2 {
fullVersionString = m[0]
versionCode, _ = strconv.Atoi(m[1])
break
}
}
if len(fullVersionString) == 0 {
return errors.New("failed to find GMSCore version string in logcat")
}
testing.ContextLogf(ctx, "ARC is using GMSCore of version %q", fullVersionString)
// Checks "variant" in GMSCore version string. Reference: go/gmscore-decoder-ring
var expectedVariant int
switch systemSdkVersion {
case 28:
// Skip checking variant for ARC P.
return nil
case 30:
expectedVariant = 15 // PROD_RVC
case 31:
expectedVariant = 19 // PROD_SC
default:
return errors.Errorf("unexpected system SDK version: %d", systemSdkVersion)
}
variant := versionCode / 10000
if variant != expectedVariant {
return errors.Errorf("ARC is using GMSCore of unexpected variant: got %d; want %d. Found GMSCore version: %q",
variant, expectedVariant, fullVersionString)
}
return nil
}