Skip to content

Commit 7fab5af

Browse files
authored
Merge pull request #226 from ndtp/2024-06-28-better_memory_handling
Improve handling of memory allocations and OOM errors
2 parents 81829cb + 12d41dd commit 7fab5af

File tree

24 files changed

+498
-48
lines changed

24 files changed

+498
-48
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Testify Change Log
22

3+
## 3.1.0
4+
5+
- https://github.com/ndtp/android-testify/pull/225 - Testify will now throw a `LowMemoryException` when attempts to allocate an `IntBuffer` fail. This can help users diagnose AVD configuration problems and reports on the state of the device.
6+
37
## 3.0.0
48

59
- https://github.com/ndtp/android-testify/pull/224 - Upgrade to Kotlin 1.9.24 and Compose to 2024.05.00

Ext/Accessibility/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ For more information about _Accessibility Checking_, please see https://develope
1818

1919
```groovy
2020
plugins {
21-
id("dev.testify") version "3.0.0" apply false
21+
id("dev.testify") version "3.1.0" apply false
2222
}
2323
```
2424

Ext/Compose/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Easily create screenshot tests for `@Composable` functions.
1212

1313
```groovy
1414
plugins {
15-
id("dev.testify") version "3.0.0" apply false
15+
id("dev.testify") version "3.1.0" apply false
1616
}
1717
```
1818

Ext/Fullscreen/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ You can set a comparison tolerance using [ScreenshotRule.setExactness](../../Lib
2020

2121
```groovy
2222
plugins {
23-
id("dev.testify") version "3.0.0" apply false
23+
id("dev.testify") version "3.1.0" apply false
2424
}
2525
```
2626

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2024 ndtp
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package dev.testify.core.processor
25+
26+
import android.app.ActivityManager
27+
import android.content.Context.ACTIVITY_SERVICE
28+
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
29+
import dev.testify.core.exception.ImageBufferAllocationException
30+
import dev.testify.core.exception.LowMemoryException
31+
import org.junit.Assert.assertEquals
32+
import org.junit.Assert.assertNotNull
33+
import org.junit.Assert.assertNotSame
34+
import org.junit.Test
35+
36+
class ImageBufferTest {
37+
38+
@Test(expected = IllegalArgumentException::class)
39+
fun must_use_positive_integer_sizes() {
40+
ImageBuffers.allocate(width = 0, height = 0, allocateDiffBuffer = false)
41+
}
42+
43+
@Test(expected = IllegalArgumentException::class)
44+
fun must_request_less_than_max_int() {
45+
ImageBuffers.allocate(width = 2048, height = 1_048_576, allocateDiffBuffer = false)
46+
}
47+
48+
@Test
49+
fun allocate_buffers() {
50+
val buffers = ImageBuffers.allocate(width = 1, height = 1, allocateDiffBuffer = false)
51+
assertNotNull(buffers.baselineBuffer)
52+
assertNotNull(buffers.currentBuffer)
53+
assertNotSame(buffers.baselineBuffer, buffers.currentBuffer)
54+
assertEquals(1, buffers.baselineBuffer.limit())
55+
assertEquals(1, buffers.currentBuffer.limit())
56+
}
57+
58+
@Test(expected = ImageBufferAllocationException::class)
59+
fun allocate_diff_buffer_defaults_to_false() {
60+
ImageBuffers.allocate(width = 1, height = 1, allocateDiffBuffer = false).diffBuffer
61+
}
62+
63+
@Test
64+
fun allocate_diff_buffer() {
65+
val buffers = ImageBuffers.allocate(width = 1, height = 1, allocateDiffBuffer = true)
66+
assertNotNull(buffers.diffBuffer)
67+
assertEquals(1, buffers.diffBuffer.limit())
68+
}
69+
70+
@Test(expected = LowMemoryException::class)
71+
fun allocate_fails_on_oom() {
72+
val activityManager = getInstrumentation().targetContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager
73+
val requestedSize: Int = activityManager.memoryClass * 1_048_576 / 2
74+
ImageBuffers.allocate(width = 1, height = requestedSize, allocateDiffBuffer = false)
75+
}
76+
77+
@Test
78+
fun can_allocate_a_reasonable_amount() {
79+
val requestedSize: Int = Runtime.getRuntime().freeMemory().toInt() / 10
80+
val buffers = ImageBuffers.allocate(width = 1, height = requestedSize, allocateDiffBuffer = false)
81+
assertNotNull(buffers.baselineBuffer)
82+
assertNotNull(buffers.currentBuffer)
83+
assertNotSame(buffers.baselineBuffer, buffers.currentBuffer)
84+
assertEquals(requestedSize, buffers.baselineBuffer.limit())
85+
assertEquals(requestedSize, buffers.currentBuffer.limit())
86+
}
87+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2024 ndtp
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package dev.testify.core.exception
25+
26+
class ImageBufferAllocationException(capacity: Int) : TestifyException(
27+
"FAILED_BUFFER_ALLOCATION",
28+
"Failed to allocate image buffer of size $capacity"
29+
)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2024 ndtp
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package dev.testify.core.exception
25+
26+
import android.content.Context
27+
import dev.testify.core.DEFAULT_FOLDER_FORMAT
28+
import dev.testify.core.DeviceStringFormatter
29+
import dev.testify.core.formatDeviceString
30+
31+
/**
32+
* An exception thrown when the current device does not have sufficient free memory to perform ParallelPixelProcessing.
33+
*
34+
* Parallel pixel processing requires the allocation of two byte buffers. For a typical device resolution of
35+
* 1080 x 1920, two buffers of approximately 2 MB each (4 MB total) are allocated. Most AVDs are configured with a heap
36+
* size of 25% their available RAM. For example, 512 MB for a device with 2 GB of RAM.
37+
* See: https://android.googlesource.com/platform/external/qemu/+/emu-master-dev/android/android-emu/android/main-common.c#1292
38+
*
39+
* This is normally more than sufficient for parallel pixel processing. However, in some instances it has been observed
40+
* where the heap size for androidTest instrumentation runners is set to only 16 MB, which is not sufficient for
41+
* Testify.
42+
*
43+
* If you encounter this exception, please verify that your emulator is correctly configured with at least 2 GB of RAM.
44+
*
45+
* @param targetContext - device context
46+
* @param requestedAllocation - number of bytes that failed to be allocated
47+
* @param memoryInfo - a string log of the device's current memory state.
48+
* @param cause - The OutOfMemoryError exception originally thrown
49+
*/
50+
class LowMemoryException(
51+
targetContext: Context,
52+
requestedAllocation: Int,
53+
memoryInfo: String,
54+
cause: OutOfMemoryError
55+
) : TestifyException(
56+
tag = "LOW_MEMORY",
57+
message = """
58+
59+
Unable to allocate $requestedAllocation bytes for parallel pixel processing.
60+
The current device ${formatDeviceString(DeviceStringFormatter(targetContext, null), DEFAULT_FOLDER_FORMAT)} does not have sufficient RAM to perform complex diff calculations.
61+
62+
Please ensure that the emulator is configured with a minimum of 2 GB of RAM.
63+
64+
Alternatively, disable the use of custom or fuzzy comparison methods:
65+
- Use a TestifyConfiguration.exactness value of null or 0.0
66+
- Set TestifyConfiguration.compareMethod to null
67+
- Do not set any exclusionRects on the TestifyConfiguration
68+
- Do not enable HighContrastDiff
69+
70+
Device Memory Info:
71+
$memoryInfo
72+
73+
Caused by ${cause::class.simpleName}:
74+
${cause.message}
75+
${cause.printStackTrace()}
76+
""".trimIndent()
77+
)

0 commit comments

Comments
 (0)