1 /*
2  * Copyright 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 androidx.benchmark
18 
19 import android.os.Build
20 import android.os.Process
21 import androidx.test.ext.junit.runners.AndroidJUnit4
22 import androidx.test.filters.MediumTest
23 import androidx.test.filters.SdkSuppress
24 import kotlin.test.assertEquals
25 import kotlin.test.assertNotNull
26 import kotlin.test.assertTrue
27 import org.junit.Test
28 import org.junit.runner.RunWith
29 
30 /**
31  * This class collects tests of strange shell behavior, for the purpose of documenting and
32  * validating how general the problems are. For this reason, these tests call directly shell
33  * commands without referring to the actual implementations in [Shell]. To test the [Shell]
34  * implementations, please add to [ShellTest].
35  */
36 @MediumTest
37 @SdkSuppress(minSdkVersion = 21)
38 @RunWith(AndroidJUnit4::class)
39 class ShellBehaviorTest {
40 
41     /**
42      * Test validates consistent behavior of pgrep, for usage in discovering processes without
43      * needing to check stderr
44      */
45     @Test
pgrepLFExecuteScriptnull46     fun pgrepLFExecuteScript() {
47 
48         val apiSpecificArgs =
49             setOfNotNull(
50                     // aosp/3507001 -> needed to print full command line (so full package name)
51                     if (Build.VERSION.SDK_INT >= 36) "-a" else null
52                 )
53                 .joinToString(" ")
54 
55         // Should only be one process - this one!
56         val pgrepOutput =
57             Shell.executeScriptCaptureStdoutStderr("pgrep -l -f $apiSpecificArgs ${Packages.TEST}")
58 
59         if (Build.VERSION.SDK_INT >= 23) {
60             // API 23 has trailing whitespace after the package name for some reason :shrug:
61             val regex = "^\\d+ ${Packages.TEST.replace(".", "\\.")}\\s*$".toRegex()
62             assertTrue(
63                 // For some reason, `stdout.contains(regex)` doesn't work :shrug:
64                 pgrepOutput.stdout.lines().any { it.matches(regex) },
65                 "expected $regex to be contained in output:\n${pgrepOutput.stdout}"
66             )
67         } else {
68             // command doesn't exist
69             assertEquals("", pgrepOutput.stdout)
70             assertTrue(pgrepOutput.stderr.isNotBlank())
71         }
72     }
73 
74     @Test
pidofnull75     fun pidof() {
76         // Should only be one process - this one!
77         val output = Shell.executeScriptCaptureStdoutStderr("pidof ${Packages.TEST}")
78         val pidofString = output.stdout.trim()
79 
80         when {
81             Build.VERSION.SDK_INT < 23 -> {
82                 // command doesn't exist
83                 assertTrue(
84                     output.stdout.isBlank() && output.stderr.isNotBlank(),
85                     "saw output $output"
86                 )
87             }
88             Build.VERSION.SDK_INT == 23 -> {
89                 // on API 23 specifically, pidof prints... all processes, ignoring the arg...
90                 assertTrue(pidofString.contains(" "))
91             }
92             else -> {
93                 assertNotNull(pidofString.toLongOrNull(), "Error, can't parse $pidofString")
94             }
95         }
96     }
97 
98     @Test
psDashAnull99     fun psDashA() {
100         val output = Shell.executeScriptCaptureStdout("ps -A").trim()
101         when {
102             Build.VERSION.SDK_INT <= 23 -> {
103                 // doesn't correctly handle -A, sometimes sees nothing, sometimes only this process
104                 val processes = output.lines()
105                 assertTrue(processes.size <= 2)
106                 assertTrue(processes.first().matches(psLabelRowRegex))
107                 if (processes.size > 1) {
108                     assertTrue(processes.last().endsWith(Packages.TEST))
109                 }
110             }
111             Build.VERSION.SDK_INT in 24..25 -> {
112                 // still doesn't support, but useful error at least
113                 assertEquals("bad pid '-A'", output)
114             }
115             else -> {
116                 // ps -A should work - expect several processes including this one
117                 val processes = output.lines()
118                 assertTrue(processes.size > 5)
119                 assertTrue(processes.first().matches(psLabelRowRegex))
120                 assertTrue(processes.any { it.endsWith(Packages.TEST) })
121             }
122         }
123     }
124 
125     /**
126      * Test validates consistent behavior of ps, for usage in checking process is alive without
127      * needing to check stderr
128      */
129     @Test
psnull130     fun ps() {
131         val output = Shell.executeScriptCaptureStdout("ps ${Process.myPid()}").trim()
132         // ps should work - expect several processes including this one
133         val lines = output.lines()
134         assertEquals(2, lines.size)
135         assertTrue(lines.first().matches(psLabelRowRegex))
136         assertTrue(lines.last().endsWith(Packages.TEST))
137     }
138 
139     companion object {
140         /**
141          * Regex for matching ps output label row
142          *
143          * Note that `ps` output changes over time, e.g.:
144          * * API 23 - `USER\s+PID\s+PPID\s+VSIZE\s+RSS\s+WCHAN\s+PC\s+NAME`
145          * * API 33 - `USER\s+PID\s+PPID\s+VSZ\s+RSS\s+WCHAN\s+ADDR\s+S\s+NAME\s`
146          */
147         val psLabelRowRegex = Regex("USER\\s+PID.+NAME\\s*")
148     }
149 }
150