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
17package android.server.am;
18
19import static android.server.am.ComponentNameUtils.getActivityName;
20import static android.server.am.UiDeviceUtils.pressHomeButton;
21import static android.server.am.debuggable.Components.DEBUGGABLE_APP_ACTIVITY;
22
23import static org.hamcrest.MatcherAssert.assertThat;
24import static org.hamcrest.Matchers.greaterThanOrEqualTo;
25import static org.junit.Assert.assertEquals;
26import static org.junit.Assert.assertTrue;
27
28import android.content.ComponentName;
29import android.support.test.InstrumentationRegistry;
30
31import org.junit.Before;
32import org.junit.Test;
33
34import java.io.File;
35import java.io.FileInputStream;
36
37/**
38 * Build/Install/Run:
39 *     atest CtsActivityManagerDeviceTestCases:ActivityManagerAmProfileTests
40 *
41 * Please talk to Android Studio team first if you want to modify or delete these tests.
42 */
43public class ActivityManagerAmProfileTests extends ActivityManagerTestBase {
44
45    private static final String OUTPUT_FILE_PATH = "/data/local/tmp/profile.trace";
46    private static final String FIRST_WORD_NO_STREAMING = "*version\n";
47    private static final String FIRST_WORD_STREAMING = "SLOW";  // Magic word set by runtime.
48
49    private String mReadableFilePath = null;
50
51    @Before
52    @Override
53    public void setUp() throws Exception {
54        super.setUp();
55        mReadableFilePath = InstrumentationRegistry.getContext()
56            .getExternalFilesDir(null)
57            .getPath() + "/profile.trace";
58    }
59
60    /**
61     * Test am profile functionality with the following 3 configurable options:
62     *    starting the activity before start profiling? yes;
63     *    sampling-based profiling? no;
64     *    using streaming output mode? no.
65     */
66    @Test
67    public void testAmProfileStartNoSamplingNoStreaming() throws Exception {
68        // am profile start ... , and the same to the following 3 test methods.
69        testProfile(true, false, false);
70    }
71
72    /**
73     * The following tests are similar to testAmProfileStartNoSamplingNoStreaming(),
74     * only different in the three configuration options.
75     */
76    @Test
77    public void testAmProfileStartNoSamplingStreaming() throws Exception {
78        testProfile(true, false, true);
79    }
80
81    @Test
82    public void testAmProfileStartSamplingNoStreaming() throws Exception {
83        testProfile(true, true, false);
84    }
85
86    @Test
87    public void testAmProfileStartSamplingStreaming() throws Exception {
88        testProfile(true, true, true);
89    }
90
91    @Test
92    public void testAmStartStartProfilerNoSamplingNoStreaming() throws Exception {
93        // am start --start-profiler ..., and the same to the following 3 test methods.
94        testProfile(false, false, false);
95    }
96
97    @Test
98    public void testAmStartStartProfilerNoSamplingStreaming() throws Exception {
99        testProfile(false, false, true);
100    }
101
102    @Test
103    public void testAmStartStartProfilerSamplingNoStreaming() throws Exception {
104        testProfile(false, true, false);
105    }
106
107    @Test
108    public void testAmStartStartProfilerSamplingStreaming() throws Exception {
109        testProfile(false, true, true);
110    }
111
112    private void testProfile(final boolean startActivityFirst, final boolean sampling,
113            final boolean streaming) throws Exception {
114        if (startActivityFirst) {
115            launchActivity(DEBUGGABLE_APP_ACTIVITY);
116        }
117
118        executeShellCommand(
119                getStartCmd(DEBUGGABLE_APP_ACTIVITY, startActivityFirst, sampling, streaming));
120        // Go to home screen and then warm start the activity to generate some interesting trace.
121        pressHomeButton();
122        launchActivity(DEBUGGABLE_APP_ACTIVITY);
123
124        executeShellCommand(getStopProfileCmd(DEBUGGABLE_APP_ACTIVITY));
125        // Sleep for 0.1 second (100 milliseconds) so the generation of the profiling
126        // file is complete.
127        try {
128            Thread.sleep(100);
129        } catch (InterruptedException e) {
130            //ignored
131        }
132        verifyOutputFileFormat(streaming);
133    }
134
135    private static String getStartCmd(final ComponentName activityName,
136            final boolean activityAlreadyStarted, final boolean sampling, final boolean streaming) {
137        final StringBuilder builder = new StringBuilder("am");
138        if (activityAlreadyStarted) {
139            builder.append(" profile start");
140        } else {
141            builder.append(String.format(" start -n %s -W -S --start-profiler %s",
142                    getActivityName(activityName), OUTPUT_FILE_PATH));
143        }
144        if (sampling) {
145            builder.append(" --sampling 1000");
146        }
147        if (streaming) {
148            builder.append(" --streaming");
149        }
150        if (activityAlreadyStarted) {
151            builder.append(String.format(
152                    " %s %s", activityName.getPackageName(), OUTPUT_FILE_PATH));
153        }
154        return builder.toString();
155    }
156
157    private static String getStopProfileCmd(final ComponentName activityName) {
158        return "am profile stop " + activityName.getPackageName();
159    }
160
161    private void verifyOutputFileFormat(final boolean streaming) throws Exception {
162        // This is a hack. The am service has to write to /data/local/tmp because it doesn't have
163        // access to the sdcard but the test app can't read there
164        executeShellCommand("mv " + OUTPUT_FILE_PATH + " " + mReadableFilePath);
165
166        final String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
167        final byte[] data = readFile(mReadableFilePath);
168        assertThat("data size", data.length, greaterThanOrEqualTo(expectedFirstWord.length()));
169        final String actualFirstWord = new String(data, 0, expectedFirstWord.length());
170        assertEquals("Unexpected first word", expectedFirstWord, actualFirstWord);
171
172        // Clean up.
173        executeShellCommand("rm -f " + OUTPUT_FILE_PATH + " " + mReadableFilePath);
174    }
175
176    private static byte[] readFile(String clientPath) throws Exception {
177        final File file = new File(clientPath);
178        assertTrue("File not found on client: " + clientPath, file.isFile());
179        final int size = (int) file.length();
180        final byte[] bytes = new byte[size];
181        try (final FileInputStream fis = new FileInputStream(file)) {
182            final int readSize = fis.read(bytes, 0, bytes.length);
183            assertEquals("Read all data", bytes.length, readSize);
184            return bytes;
185        }
186    }
187}
188