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