1 /*
<lambda>null2  * 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 androidx.test.ext.junit.runners.AndroidJUnit4
20 import androidx.test.filters.SdkSuppress
21 import androidx.test.filters.SmallTest
22 import java.io.File
23 import java.util.Date
24 import kotlin.test.assertEquals
25 import kotlin.test.assertFailsWith
26 import kotlin.test.assertFalse
27 import kotlin.test.assertTrue
28 import org.junit.Assert
29 import org.junit.Assume.assumeTrue
30 import org.junit.Before
31 import org.junit.Test
32 import org.junit.runner.RunWith
33 
34 @SmallTest
35 @RunWith(AndroidJUnit4::class)
36 public class OutputsTest {
37     private val outputs: MutableList<String> = mutableListOf()
38 
39     @Before
40     public fun setUp() {
41         outputs.addAll(
42             // Don't add the / prefix.
43             listOf(
44                 "foo/a.txt",
45                 "foo/b.txt",
46                 "foo/bar/a.txt",
47                 "foo/bar/baz/a.txt",
48             )
49         )
50     }
51 
52     @Test
53     public fun testRelativePaths_usesIntendedOutputDirectory() {
54         assertRelativePaths(Outputs.outputDirectory, outputs)
55     }
56 
57     @Test
58     @SdkSuppress(minSdkVersion = 30, maxSdkVersion = 30)
59     public fun testRelativePaths_usesDirectoryUsableByAppAndShell() {
60         assertRelativePaths(Outputs.dirUsableByAppAndShell, outputs)
61     }
62 
63     /**
64      * This test validates the strings searched for by BPGP aren't broken by filename sanitization.
65      */
66     @Test
67     public fun sanitizeFilename_baselineProfileGradlePlugin() {
68         val baselineSanitized = Outputs.sanitizeFilename("foo[a=b]-baseline-prof-001.txt")
69         val startupSanitized = Outputs.sanitizeFilename("foo[a=b]-startup-prof-001.txt")
70 
71         assertEquals("foo_a_b_-baseline-prof-001.txt", baselineSanitized)
72         assertEquals("foo_a_b_-startup-prof-001.txt", startupSanitized)
73         baselineSanitized.apply {
74             // NOTE: behavior MUST MATCH baseline profile gradle plugin check, see b/303034735
75             assertTrue(contains("-baseline-prof-") && endsWith(".txt"))
76         }
77         startupSanitized.apply {
78             // NOTE: behavior MUST MATCH baseline profile gradle plugin check, see b/303034735
79             assertTrue(contains("-startup-prof-") && endsWith(".txt"))
80         }
81     }
82 
83     @Test
84     public fun sanitizeFilename() {
85         assertEquals(
86             "testFilename_one_Thing_two_other_",
87             Outputs.sanitizeFilename("testFilename[one=Thing( ),two:other]")
88         )
89     }
90 
91     @Test
92     public fun sanitizeFilename_tooLong() {
93         assertEquals("a".repeat(199), Outputs.sanitizeFilename("a".repeat(199)))
94         assertFailsWith<IllegalArgumentException> { Outputs.sanitizeFilename("a".repeat(200)) }
95     }
96 
97     @Test
98     public fun sanitizeFilename_withExtension() {
99         assertEquals(
100             "testFilename_one_Thing_two_other_.trace",
101             Outputs.sanitizeFilename("testFilename[one=Thing( ),two:other].trace")
102         )
103     }
104 
105     @Test
106     public fun testDateToFileName() {
107         val date = Date(0)
108         val expected = "1970-01-01-00-00-00"
109         assertEquals(Outputs.dateToFileName(date), expected)
110     }
111 
112     private fun assertRelativePaths(base: File, paths: List<String>) {
113         val basePath = base.absolutePath
114         val relativePaths = paths.map { Outputs.relativePathFor(File(base, it).absolutePath) }
115         relativePaths.forEach { path ->
116             assertFalse(path.startsWith("/"), "$path cannot start with a `/`.")
117             assertFalse(
118                 path.startsWith(basePath),
119                 "Invalid relative path ($path), Base ($basePath)."
120             )
121         }
122 
123         for ((path, relativePath) in paths.zip(relativePaths)) {
124             assertEquals(path, relativePath, "$path != $relativePath")
125         }
126     }
127 
128     @Test
129     public fun dirUsableByAppAndShell_writeAppReadApp() {
130         val dir = Outputs.dirUsableByAppAndShell
131         val file = File.createTempFile("testFile", null, dir)
132         try {
133             file.writeText(file.name) // use name, as it's fairly unique
134             Assert.assertEquals(file.name, file.readText())
135         } finally {
136             file.delete()
137         }
138     }
139 
140     @Test
141     @SdkSuppress(minSdkVersion = 21)
142     public fun dirUsableByAppAndShell_writeAppReadShell() {
143         val dir = Outputs.dirUsableByAppAndShell
144         val file = File.createTempFile("testFile", null, dir)
145         file.setReadable(true, false)
146         try {
147             file.writeText(file.name) // use name, as it's fairly unique
148             Assert.assertEquals(
149                 file.name,
150                 Shell.executeScriptCaptureStdout("cat ${file.absolutePath}")
151             )
152         } finally {
153             file.delete()
154         }
155     }
156 
157     @Test
158     @SdkSuppress(minSdkVersion = 21)
159     public fun dirUsableByAppAndShell_writeShellReadShell() {
160         val dir = Outputs.dirUsableByAppAndShell
161 
162         // simple way to get a unique path, not shared across runs
163         val file = File.createTempFile("shellwrite", null, dir)
164         val path = file.absolutePath
165         file.delete()
166 
167         Shell.executeScriptSilent("rm -f $path")
168         try {
169             Shell.executeScriptSilent("echo test > $path")
170             assertEquals("test\n", Shell.executeScriptCaptureStdout("cat $path"))
171             file.appendBytes("extra".toByteArray())
172         } finally {
173             Shell.executeScriptSilent("rm -f $path")
174         }
175     }
176 
177     @Test
178     @SdkSuppress(minSdkVersion = 21)
179     public fun dirUsableByAppAndShell_writeShellReadApp() {
180         val dir = Outputs.dirUsableByAppAndShell
181 
182         // simple way to get a unique path, not shared across runs
183         val file = File.createTempFile("shellwrite", null, dir)
184         val path = file.absolutePath
185         file.delete()
186 
187         Shell.executeScriptSilent("rm -f $path")
188         try {
189             Shell.executeScriptSilent("echo test > $path")
190             assertEquals("test\n", File(path).readText())
191             file.appendBytes("extra".toByteArray())
192         } finally {
193             Shell.executeScriptSilent("rm -f $path")
194         }
195     }
196 
197     /**
198      * NOTE: this test checks that the instrumentation argument additionalTestOutputDir isn't set to
199      * an invalid / unusable location.
200      *
201      * Running through Studio/Gradle, this isn't defined by the library, it's defined by AGP.
202      *
203      * If this test fails, we need to handle the directory differently.
204      */
205     @Test
206     fun additionalTestOutputDir_appWrite() {
207         val additionalTestOutputDir = Arguments.additionalTestOutputDir
208         assumeTrue(additionalTestOutputDir != null)
209         val file = File.createTempFile("testFile", null, File(additionalTestOutputDir!!))
210         try {
211             file.writeText("testString")
212         } finally {
213             file.delete()
214         }
215     }
216 }
217