Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
*/
package dev.testify.core.processor

import android.app.ActivityManager
import android.content.Context.ACTIVITY_SERVICE
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import dev.testify.core.exception.ImageBufferAllocationException
import dev.testify.core.exception.LowMemoryException
import org.junit.Assert.assertEquals
Expand Down Expand Up @@ -69,14 +66,16 @@ class ImageBufferTest {

@Test(expected = LowMemoryException::class)
fun allocate_fails_on_oom() {
val activityManager = getInstrumentation().targetContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager
val requestedSize: Int = activityManager.memoryClass * 1_048_576 / 2
// Request a capacity whose byte size (capacity * 4) exceeds Int.MAX_VALUE,
// which cannot be fulfilled by a single direct ByteBuffer.
val requestedSize: Int = Int.MAX_VALUE / 2
ImageBuffers.allocate(width = 1, height = requestedSize, allocateDiffBuffer = false)
}

@Test
fun can_allocate_a_reasonable_amount() {
val requestedSize: Int = Runtime.getRuntime().freeMemory().toInt() / 10
// Allocate buffers equivalent to a 1080x1920 screen (a typical device resolution)
val requestedSize = 1080 * 1920
val buffers = ImageBuffers.allocate(width = 1, height = requestedSize, allocateDiffBuffer = false)
assertNotNull(buffers.baselineBuffer)
assertNotNull(buffers.currentBuffer)
Expand Down
2 changes: 2 additions & 0 deletions Library/src/main/java/dev/testify/ScreenshotUtility.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ fun createBitmapFromActivity(

val destination = getDestination(activity, fileName)
saveBitmapToDestination(activity, currentActivityBitmap[0], destination)
currentActivityBitmap[0]?.recycle()
currentActivityBitmap[0] = null
return destination.loadBitmap(preferredBitmapOptions)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import dev.testify.core.formatDeviceString
@Suppress("ktlint:standard:argument-list-wrapping")
class LowMemoryException(
targetContext: Context,
requestedAllocation: Int,
requestedAllocation: Long,
memoryInfo: String,
cause: OutOfMemoryError
) : TestifyException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import androidx.annotation.IntRange
import androidx.test.platform.app.InstrumentationRegistry
import dev.testify.core.exception.ImageBufferAllocationException
import dev.testify.core.exception.LowMemoryException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.IntBuffer

private const val INTEGER_BYTES: Int = 4
Expand Down Expand Up @@ -122,11 +124,16 @@ internal data class ImageBuffers(
* @throws LowMemoryException if the allocation fails twice
*/
internal fun allocateSafely(capacity: Int, retry: Boolean = true): IntBuffer {
val requestedBytes = capacity * INTEGER_BYTES
val requestedBytes = capacity.toLong() * INTEGER_BYTES
return try {
val memoryState = formatMemoryState()
Log.v(LOG_TAG, "Allocating $requestedBytes bytes\n$memoryState")
IntBuffer.allocate(capacity)
if (requestedBytes > Int.MAX_VALUE) {
throw OutOfMemoryError("Requested allocation of $requestedBytes bytes exceeds maximum direct buffer size")
}
ByteBuffer.allocateDirect(requestedBytes.toInt())
.order(ByteOrder.nativeOrder())
.asIntBuffer()
} catch (e: OutOfMemoryError) {
val memoryState = formatMemoryState()
Log.e(LOG_TAG, "Error allocating $requestedBytes bytes\n$memoryState", e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,23 +133,26 @@ class ParallelPixelProcessor private constructor(
*/
fun analyze(analyzer: AnalyzePixelFunction): Boolean {
val buffers = prepareBuffers()
val chunkData = getChunkData(buffers.width, buffers.height)
val results = BitSet(chunkData.chunks).apply { set(0, chunkData.chunks) }

runBlockingInChunks(chunkData) { chunk, index ->
val position = getPosition(index, buffers.width)
val baselinePixel = buffers.baselineBuffer[index]
val currentPixel = buffers.currentBuffer[index]
if (!analyzer(baselinePixel, currentPixel, position)) {
results.clear(chunk)
false
} else {
true
try {
val chunkData = getChunkData(buffers.width, buffers.height)
val results = BitSet(chunkData.chunks).apply { set(0, chunkData.chunks) }

runBlockingInChunks(chunkData) { chunk, index ->
val position = getPosition(index, buffers.width)
val baselinePixel = buffers.baselineBuffer[index]
val currentPixel = buffers.currentBuffer[index]
if (!analyzer(baselinePixel, currentPixel, position)) {
results.clear(chunk)
false
} else {
true
}
}
}

buffers.free()
return results.cardinality() == chunkData.chunks
return results.cardinality() == chunkData.chunks
} finally {
buffers.free()
}
}

/**
Expand All @@ -163,25 +166,30 @@ class ParallelPixelProcessor private constructor(
transformer: (baselinePixel: Int, currentPixel: Int, position: Pair<Int, Int>) -> Int
): TransformResult {
val buffers = prepareBuffers(allocateDiffBuffer = true)
val chunkData = getChunkData(buffers.width, buffers.height)
val diffBuffer = buffers.diffBuffer

runBlockingInChunks(chunkData) { _, index ->
val position = getPosition(index, buffers.width)
val baselinePixel = buffers.baselineBuffer[index]
val currentPixel = buffers.currentBuffer[index]
diffBuffer.put(index, transformer(baselinePixel, currentPixel, position))
true
}

val result = TransformResult(
width = buffers.width,
height = buffers.height,
pixels = diffBuffer.array()
)
try {
val chunkData = getChunkData(buffers.width, buffers.height)
val diffBuffer = buffers.diffBuffer

runBlockingInChunks(chunkData) { _, index ->
val position = getPosition(index, buffers.width)
val baselinePixel = buffers.baselineBuffer[index]
val currentPixel = buffers.currentBuffer[index]
diffBuffer.put(index, transformer(baselinePixel, currentPixel, position))
true
}

buffers.free()
return result
val pixels = IntArray(buffers.width * buffers.height)
diffBuffer.position(0)
diffBuffer.get(pixels)

return TransformResult(
width = buffers.width,
height = buffers.height,
pixels = pixels
)
} finally {
buffers.free()
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,16 @@ class HighContrastDiff private constructor(
}
}

saveBitmapToDestination(
context = context,
bitmap = transformResult.createBitmap(),
destination = getDestination(context, "$fileName.diff")
)
val diffBitmap = transformResult.createBitmap()
try {
saveBitmapToDestination(
context = context,
bitmap = diffBitmap,
destination = getDestination(context, "$fileName.diff")
)
} finally {
diffBitmap.recycle()
}
}

private var exactness: Float? = null
Expand Down