blob: 4a2d87c614df71762a1d7621be9819b523e13b56 [file] [log] [blame]
/*
* Copyright (C) 2023 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.tradefed.targetprep.sync;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.ZipUtil2;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Truth;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/** Basic test to start iterating on device incremental image. */
@RunWith(DeviceJUnit4ClassRunner.class)
public class IncrementalImageFuncTest extends BaseHostJUnit4Test {
public static final Set<String> PARTITIONS_TO_DIFF =
ImmutableSet.of(
"product.img",
"system.img",
"system_dlkm.img",
"system_ext.img",
"vendor.img",
"vendor_dlkm.img");
public static class TrackResults {
public String imageMd5;
public String mountedBlock;
@Override
public String toString() {
return "TrackResults [imageMd5=" + imageMd5 + ", mountedBlock=" + mountedBlock + "]";
}
}
private Map<String, TrackResults> partitionToInfo = new ConcurrentHashMap<>();
@Test
public void testBlockCompareUpdate() throws Exception {
String originalBuildId = getDevice().getBuildId();
CLog.d("Original build id: %s", originalBuildId);
File blockCompare = getBuild().getFile("block-compare");
FileUtil.chmodGroupRWX(blockCompare);
File srcImage = getBuild().getFile("src-image");
File srcDirectory = ZipUtil2.extractZipToTemp(srcImage, "incremental_src");
File targetImage = getBuild().getFile("target-image");
File targetDirectory = ZipUtil2.extractZipToTemp(targetImage, "incremental_target");
File workDir = FileUtil.createTempDir("block_compare_workdir");
try {
for (String partition : PARTITIONS_TO_DIFF) {
File possibleSrc = new File(srcDirectory, partition);
File possibleTarget = new File(targetDirectory, partition);
if (possibleSrc.exists() && possibleTarget.exists()) {
blockCompare(blockCompare, possibleSrc, possibleTarget, workDir);
TrackResults newRes = new TrackResults();
newRes.imageMd5 = FileUtil.calculateMd5(possibleTarget);
partitionToInfo.put(FileUtil.getBaseName(partition), newRes);
} else {
CLog.e("Skipping %s no src or target", partition);
}
}
inspectCowPatches(workDir);
getDevice().executeShellV2Command("mkdir -p /data/ndb");
getDevice().executeShellV2Command("rm -rf /data/ndb/*.patch");
// Ensure snapshotctl exists
CommandResult whichOutput = getDevice().executeShellV2Command("which snapshotctl");
CLog.e("stdout: %s, stderr: %s", whichOutput.getStdout(), whichOutput.getStderr());
getDevice().executeShellV2Command("snapshotctl unmap-snapshots");
getDevice().executeShellV2Command("snapshotctl delete-snapshots");
for (File f : workDir.listFiles()) {
try (CloseableTraceScope ignored = new CloseableTraceScope("push:" + f.getName())) {
boolean success;
if (f.isDirectory()) {
success = getDevice().pushDir(f, "/data/ndb/");
} else {
success = getDevice().pushFile(f, "/data/ndb/" + f.getName());
}
CLog.e("Push successful.: %s. %s->%s", success, f, "/data/ndb/" + f.getName());
assertTrue(success);
}
}
CommandResult mapOutput =
getDevice().executeShellV2Command("snapshotctl map-snapshots /data/ndb/");
CLog.e("stdout: %s, stderr: %s", mapOutput.getStdout(), mapOutput.getStderr());
if (!CommandStatus.SUCCESS.equals(mapOutput.getStatus())) {
fail("Failed to map the snapshots.");
}
getDevice().reboot();
// Do Validation
getDevice().enableAdbRoot();
CommandResult psOutput = getDevice().executeShellV2Command("ps -ef | grep snapuserd");
CLog.d("stdout: %s, stderr: %s", psOutput.getStdout(), psOutput.getStderr());
listMappingAndCompare(partitionToInfo);
String afterMountBuildId = getDevice().getBuildId();
CLog.d(
"Original build id: %s. after mount build id: %s",
originalBuildId, afterMountBuildId);
} finally {
FileUtil.recursiveDelete(workDir);
FileUtil.recursiveDelete(srcDirectory);
FileUtil.recursiveDelete(targetDirectory);
revertToPreviousBuild();
}
}
private void blockCompare(File blockCompare, File srcImage, File targetImage, File workDir) {
try (CloseableTraceScope ignored =
new CloseableTraceScope("block_compare:" + srcImage.getName())) {
IRunUtil runUtil = new RunUtil();
runUtil.setWorkingDir(workDir);
CommandResult result =
runUtil.runTimedCmd(
0L,
blockCompare.getAbsolutePath(),
srcImage.getAbsolutePath(),
targetImage.getAbsolutePath());
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
throw new RuntimeException(
String.format("%s\n%s", result.getStdout(), result.getStderr()));
}
File[] listFiles = workDir.listFiles();
CLog.e("%s", Arrays.asList(listFiles));
}
}
private void inspectCowPatches(File workDir) throws IOException {
File inspectZip = getBuild().getFile("inspect_cow.zip");
if (inspectZip == null) {
return;
}
File destDir = ZipUtil2.extractZipToTemp(inspectZip, "inspect_cow_unzip");
File inspect = FileUtil.findFile(destDir, "inspect_cow");
FileUtil.chmodGroupRWX(inspect);
IRunUtil runUtil = new RunUtil();
long sizeOfPatches = 0L;
try (CloseableTraceScope ignored = new CloseableTraceScope("inspect_cow")) {
for (File f : workDir.listFiles()) {
CommandResult result =
runUtil.runTimedCmd(0L, inspect.getAbsolutePath(), f.getAbsolutePath());
CLog.d("Status: %s", result.getStatus());
CLog.d("Stdout: %s", result.getStdout());
CLog.d("Stderr: %s", result.getStderr());
CLog.d("Patch size: %s", f.length());
sizeOfPatches += f.length();
}
CLog.d("Total size of patches: %s", sizeOfPatches);
} finally {
FileUtil.recursiveDelete(destDir);
}
}
private void listMappingAndCompare(Map<String, TrackResults> partitionToInfo)
throws DeviceNotAvailableException {
CommandResult lsOutput = getDevice().executeShellV2Command("ls -l /dev/block/mapper/");
CLog.d("stdout: %s, stderr: %s", lsOutput.getStdout(), lsOutput.getStderr());
for (String lines : lsOutput.getStdout().split("\n")) {
if (!lines.contains("->")) {
continue;
}
String[] pieces = lines.split(" ");
String partition = pieces[8].substring(0, pieces[8].length() - 2);
CLog.d("Partition extracted: %s", partition);
if (partitionToInfo.containsKey(partition)) {
partitionToInfo.get(partition).mountedBlock = pieces[10];
}
}
CLog.d("Infos: %s", partitionToInfo);
for (Entry<String, TrackResults> res : partitionToInfo.entrySet()) {
if (res.getValue().mountedBlock == null) {
CLog.e("No partition found in mapping for %s", res);
continue;
}
TrackResults result = res.getValue();
CommandResult md5Output =
getDevice().executeShellV2Command("md5sum " + result.mountedBlock);
CLog.d("stdout: %s, stderr: %s", md5Output.getStdout(), md5Output.getStderr());
if (!CommandStatus.SUCCESS.equals(md5Output.getStatus())) {
fail("Fail to get md5sum from " + result.mountedBlock);
}
String md5device = md5Output.getStdout().trim().split("\\s+")[0];
Truth.assertThat(result.imageMd5).isEqualTo(md5device);
}
}
private void revertToPreviousBuild() throws DeviceNotAvailableException {
getDevice().executeShellV2Command("rm -f /metadata/ota/snapshot-boot");
getDevice().reboot();
}
}