1 /*
2  * Copyright (C) 2021 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.compos.test;
18 
19 import static com.android.microdroid.test.host.CommandResultSubject.assertThat;
20 import static com.android.microdroid.test.host.CommandResultSubject.command_results;
21 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.junit.Assume.assumeFalse;
27 import static org.junit.Assume.assumeTrue;
28 
29 import android.platform.test.annotations.RootPermissionTest;
30 
31 import com.android.microdroid.test.host.CommandRunner;
32 import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
33 import com.android.tradefed.device.TestDevice;
34 import com.android.tradefed.log.LogUtil.CLog;
35 import com.android.tradefed.result.FileInputStreamSource;
36 import com.android.tradefed.result.LogDataType;
37 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
38 import com.android.tradefed.util.CommandResult;
39 import com.android.tradefed.util.RunUtil;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Rule;
44 import org.junit.Test;
45 import org.junit.rules.TestName;
46 import org.junit.runner.RunWith;
47 
48 import java.io.File;
49 
50 @RootPermissionTest
51 @RunWith(DeviceJUnit4ClassRunner.class)
52 public final class ComposTestCase extends MicrodroidHostTestCaseBase {
53 
54     // Binaries used in test. (These paths are valid both in host and Microdroid.)
55     private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh";
56     private static final String COMPOSD_CMD_BIN = "/apex/com.android.compos/bin/composd_cmd";
57     private static final String COMPOS_VERIFY_BIN =
58             "/apex/com.android.compos/bin/compos_verify";
59 
60     private static final String COMPOS_APEXDATA_DIR = "/data/misc/apexdata/com.android.compos";
61 
62     /** Output directory of odrefresh */
63     private static final String TEST_ARTIFACTS_DIR = "test-artifacts";
64 
65     private static final String ODREFRESH_OUTPUT_DIR =
66             "/data/misc/apexdata/com.android.art/" + TEST_ARTIFACTS_DIR;
67 
68     /** Timeout of odrefresh to finish */
69     private static final int ODREFRESH_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
70 
71     // ExitCode expanded from art/odrefresh/include/odrefresh/odrefresh.h.
72     private static final int OKAY = 0;
73     private static final int COMPILATION_SUCCESS = 80;
74 
75     // Files that define the "test" instance of CompOS
76     private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/";
77 
78     private static final String SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME =
79             "dalvik.vm.systemservercompilerfilter";
80     private String mBackupSystemServerCompilerFilter;
81 
82     @Rule public TestLogData mTestLogs = new TestLogData();
83     @Rule public TestName mTestName = new TestName();
84 
85     @Before
setUp()86     public void setUp() throws Exception {
87         assumeDeviceIsCapable(getDevice());
88         // Test takes too long to run on Cuttlefish (b/292824951).
89         assumeFalse("Skipping test on Cuttlefish", isCuttlefish());
90         // CompOS requires a protected VM
91         assumeTrue(((TestDevice) getDevice()).supportsMicrodroid(/*protectedVm*/ true));
92 
93         String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME);
94         if (value == null) {
95             mBackupSystemServerCompilerFilter = "";
96         } else {
97             mBackupSystemServerCompilerFilter = value;
98         }
99     }
100 
101     @After
tearDown()102     public void tearDown() throws Exception {
103         killVmAndReconnectAdb();
104 
105         CommandRunner android = new CommandRunner(getDevice());
106 
107         // Clear up any CompOS instance files we created
108         android.tryRun("rm", "-rf", COMPOS_TEST_ROOT);
109 
110         // And any artifacts generated by odrefresh
111         android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR);
112 
113         if (mBackupSystemServerCompilerFilter != null) {
114             CLog.d("Restore dalvik.vm.systemservercompilerfilter to "
115                     + mBackupSystemServerCompilerFilter);
116             getDevice().setProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME,
117                     mBackupSystemServerCompilerFilter);
118         }
119     }
120 
121     @Test
testOdrefreshSpeed()122     public void testOdrefreshSpeed() throws Exception {
123         setPropertyOrThrow(getDevice(), SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed");
124         testOdrefresh();
125     }
126 
127     @Test
testOdrefreshSpeedProfile()128     public void testOdrefreshSpeedProfile() throws Exception {
129         setPropertyOrThrow(getDevice(), SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed-profile");
130         testOdrefresh();
131     }
132 
testOdrefresh()133     private void testOdrefresh() throws Exception {
134         CommandRunner android = new CommandRunner(getDevice());
135 
136         // Prepare the groundtruth. The compilation on Android should finish successfully.
137         {
138             long start = System.currentTimeMillis();
139             CommandResult result = runOdrefresh(android, "--force-compile");
140             long elapsed = System.currentTimeMillis() - start;
141             assertThat(result).exitCode().isEqualTo(COMPILATION_SUCCESS);
142             CLog.i("Local compilation took " + elapsed + "ms");
143         }
144 
145         // Save the expected checksum for the output directory.
146         String expectedChecksumSnapshot = checksumDirectoryContentPartial(android,
147                 ODREFRESH_OUTPUT_DIR);
148 
149         // --check may delete the output.
150         CommandResult result = runOdrefresh(android, "--check");
151         assertThat(result).exitCode().isEqualTo(OKAY);
152 
153         // Expect the compilation in Compilation OS to finish successfully.
154         {
155             long start = System.currentTimeMillis();
156             result =
157                     android.runForResultWithTimeout(
158                             ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN, "test-compile");
159             long elapsed = System.currentTimeMillis() - start;
160             assertThat(result).exitCode().isEqualTo(0);
161             CLog.i("Comp OS compilation took " + elapsed + "ms");
162         }
163         killVmAndReconnectAdb();
164 
165         // Expect the BCC extracted from the BCC to be well-formed.
166         assertVmBccIsValid();
167 
168         // Save the actual checksum for the output directory.
169         String actualChecksumSnapshot = checksumDirectoryContentPartial(android,
170                 ODREFRESH_OUTPUT_DIR);
171 
172         // Expect the output of Comp OS to be the same as compiled on Android.
173         assertThat(actualChecksumSnapshot).isEqualTo(expectedChecksumSnapshot);
174 
175         // Expect extra files generated by CompOS exist.
176         android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info");
177         android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info.signature");
178 
179         // Expect the CompOS signature to be valid
180         android.run(COMPOS_VERIFY_BIN + " --debug --instance test");
181     }
182 
assertVmBccIsValid()183     private void assertVmBccIsValid() throws Exception {
184         File bcc_file = getDevice().pullFile(COMPOS_APEXDATA_DIR + "/test/bcc");
185         assertThat(bcc_file).isNotNull();
186 
187         // Add the BCC to test artifacts, in case it is ill-formed or otherwise interesting.
188         mTestLogs.addTestLog(bcc_file.getPath(), LogDataType.UNKNOWN,
189                 new FileInputStreamSource(bcc_file));
190 
191         // Find the validator binary - note that it's specified as a dependency in our Android.bp.
192         File validator = getTestInformation().getDependencyFile("hwtrust", /*targetFirst=*/ false);
193 
194         CommandResult result =
195                 new RunUtil()
196                         .runTimedCmd(
197                                 10000,
198                                 validator.getAbsolutePath(),
199                                 "dice-chain",
200                                 bcc_file.getAbsolutePath());
201         assertWithMessage("hwtrust failed").about(command_results()).that(result).isSuccess();
202     }
203 
runOdrefresh(CommandRunner android, String command)204     private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception {
205         return android.runForResultWithTimeout(
206                 ODREFRESH_TIMEOUT_MS,
207                 ODREFRESH_BIN,
208                 "--dalvik-cache=" + TEST_ARTIFACTS_DIR,
209                 command);
210     }
211 
killVmAndReconnectAdb()212     private void killVmAndReconnectAdb() throws Exception {
213         CommandRunner android = new CommandRunner(getDevice());
214 
215         android.tryRun("killall", "crosvm");
216         android.tryRun("stop", "virtualizationservice");
217 
218         // Delete stale data
219         android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
220     }
221 
checksumDirectoryContentPartial(CommandRunner runner, String path)222     private String checksumDirectoryContentPartial(CommandRunner runner, String path)
223             throws Exception {
224         // Sort by filename (second column) to make comparison easier. Filter out compos.info and
225         // compos.info.signature since it's only generated by CompOS.
226         // TODO(b/211458160): Remove cache-info.xml once we can plumb timestamp and isFactory of
227         // APEXes to the VM.
228         return runner.run(
229                 "cd "
230                         + path
231                         + " && find -type f -exec sha256sum {} \\;"
232                         + "| grep -v cache-info.xml | grep -v compos.info"
233                         + "| sort -k2");
234     }
235 }
236