diff options
author | 2024-12-13 09:27:57 -0800 | |
---|---|---|
committer | 2024-12-13 13:54:14 -0800 | |
commit | 5fa1762976176a585bb963e9033713c705513577 (patch) | |
tree | 27dd8b836dbed4a828728f70b08d090947d4b87c /tests | |
parent | ba74cdad9bb6fe8a824f787e776b0a414e8fd495 (diff) |
Remove released preview_image_loader feature flag.
The flag was released with 24Q4.
Unused ImageLoader implementations, CachingImagePreviewImageLoader and
ImagePreviewImageLoader, are deleted.
Bug: 348665058
Bug: 343819590
Test: atest IntentResolver-tests-unit
Test: atest IntentResolver-tests-activity
Flag: EXEMPT flag removal
Change-Id: Ie6a090b724bfae9355f6c2dace5d6952140c92f3
Diffstat (limited to 'tests')
2 files changed, 0 insertions, 655 deletions
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt deleted file mode 100644 index d5a569aa..00000000 --- a/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.contentpreview - -import android.graphics.Bitmap -import android.net.Uri -import android.util.Size -import com.google.common.truth.Truth.assertThat -import kotlin.math.ceil -import kotlin.math.roundToInt -import kotlin.time.Duration.Companion.milliseconds -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Test - -@OptIn(ExperimentalCoroutinesApi::class) -class CachingImagePreviewImageLoaderTest { - - private val testDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testDispatcher) - private val testJobTime = 100.milliseconds - private val testCacheSize = 4 - private val testMaxConcurrency = 2 - private val testTimeToFillCache = - testJobTime * ceil((testCacheSize).toFloat() / testMaxConcurrency.toFloat()).roundToInt() - private val testUris = - List(5) { Uri.fromParts("TestScheme$it", "TestSsp$it", "TestFragment$it") } - private val previewSize = Size(500, 500) - private val testTimeToLoadAllUris = - testJobTime * ceil((testUris.size).toFloat() / testMaxConcurrency.toFloat()).roundToInt() - private val testBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8) - private val fakeThumbnailLoader = - FakeThumbnailLoader().apply { - testUris.forEach { - fakeInvoke[it] = { - delay(testJobTime) - testBitmap - } - } - } - - private val imageLoader = - CachingImagePreviewImageLoader( - scope = testScope.backgroundScope, - bgDispatcher = testDispatcher, - thumbnailLoader = fakeThumbnailLoader, - cacheSize = testCacheSize, - maxConcurrency = testMaxConcurrency, - ) - - @Test - fun loadImage_notCached_callsThumbnailLoader() = - testScope.runTest { - // Arrange - var result: Bitmap? = null - - // Act - imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it } - advanceTimeBy(testJobTime) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0]) - assertThat(result).isSameInstanceAs(testBitmap) - } - - @Test - fun loadImage_cached_usesCachedValue() = - testScope.runTest { - // Arrange - imageLoader.loadImage(testScope, testUris[0], previewSize) {} - advanceTimeBy(testJobTime) - runCurrent() - fakeThumbnailLoader.invokeCalls.clear() - var result: Bitmap? = null - - // Act - imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it } - advanceTimeBy(testJobTime) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() - assertThat(result).isSameInstanceAs(testBitmap) - } - - @Test - fun loadImage_error_returnsNull() = - testScope.runTest { - // Arrange - fakeThumbnailLoader.fakeInvoke[testUris[0]] = { - delay(testJobTime) - throw RuntimeException("Test exception") - } - var result: Bitmap? = testBitmap - - // Act - imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it } - advanceTimeBy(testJobTime) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0]) - assertThat(result).isNull() - } - - @Test - fun loadImage_uncached_limitsConcurrency() = - testScope.runTest { - // Arrange - val results = mutableListOf<Bitmap?>() - assertThat(testUris.size).isGreaterThan(testMaxConcurrency) - - // Act - testUris.take(testMaxConcurrency + 1).forEach { uri -> - imageLoader.loadImage(testScope, uri, previewSize) { results.add(it) } - } - - // Assert - assertThat(results).isEmpty() - advanceTimeBy(testJobTime) - runCurrent() - assertThat(results).hasSize(testMaxConcurrency) - advanceTimeBy(testJobTime) - runCurrent() - assertThat(results).hasSize(testMaxConcurrency + 1) - assertThat(results) - .containsExactlyElementsIn(List(testMaxConcurrency + 1) { testBitmap }) - } - - @Test - fun loadImage_cacheEvicted_cancelsLoadAndReturnsNull() = - testScope.runTest { - // Arrange - val results = MutableList<Bitmap?>(testUris.size) { null } - assertThat(testUris.size).isGreaterThan(testCacheSize) - - // Act - imageLoader.loadImage(testScope, testUris[0], previewSize) { results[0] = it } - runCurrent() - testUris.indices.drop(1).take(testCacheSize).forEach { i -> - imageLoader.loadImage(testScope, testUris[i], previewSize) { results[i] = it } - } - advanceTimeBy(testTimeToFillCache) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).containsExactlyElementsIn(testUris) - assertThat(results) - .containsExactlyElementsIn( - List(testUris.size) { index -> if (index == 0) null else testBitmap } - ) - .inOrder() - assertThat(fakeThumbnailLoader.unfinishedInvokeCount).isEqualTo(1) - } - - @Test - fun prePopulate_fillsCache() = - testScope.runTest { - // Arrange - val fullCacheUris = testUris.take(testCacheSize) - assertThat(fullCacheUris).hasSize(testCacheSize) - - // Act - imageLoader.prePopulate(fullCacheUris.map { it to previewSize }) - advanceTimeBy(testTimeToFillCache) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).containsExactlyElementsIn(fullCacheUris) - - // Act - fakeThumbnailLoader.invokeCalls.clear() - imageLoader.prePopulate(fullCacheUris.map { it to previewSize }) - advanceTimeBy(testTimeToFillCache) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() - } - - @Test - fun prePopulate_greaterThanCacheSize_fillsCacheThenDropsRemaining() = - testScope.runTest { - // Arrange - assertThat(testUris.size).isGreaterThan(testCacheSize) - - // Act - imageLoader.prePopulate(testUris.map { it to previewSize }) - advanceTimeBy(testTimeToLoadAllUris) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls) - .containsExactlyElementsIn(testUris.take(testCacheSize)) - - // Act - fakeThumbnailLoader.invokeCalls.clear() - imageLoader.prePopulate(testUris.map { it to previewSize }) - advanceTimeBy(testTimeToLoadAllUris) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() - } - - @Test - fun prePopulate_fewerThatCacheSize_loadsTheGiven() = - testScope.runTest { - // Arrange - val unfilledCacheUris = testUris.take(testMaxConcurrency) - assertThat(unfilledCacheUris.size).isLessThan(testCacheSize) - - // Act - imageLoader.prePopulate(unfilledCacheUris.map { it to previewSize }) - advanceTimeBy(testJobTime) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).containsExactlyElementsIn(unfilledCacheUris) - - // Act - fakeThumbnailLoader.invokeCalls.clear() - imageLoader.prePopulate(unfilledCacheUris.map { it to previewSize }) - advanceTimeBy(testJobTime) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).isEmpty() - } - - @Test - fun invoke_uncached_alwaysCallsTheThumbnailLoader() = - testScope.runTest { - // Arrange - - // Act - imageLoader.invoke(testUris[0], previewSize, caching = false) - imageLoader.invoke(testUris[0], previewSize, caching = false) - advanceTimeBy(testJobTime) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0], testUris[0]) - } - - @Test - fun invoke_cached_usesTheCacheWhenPossible() = - testScope.runTest { - // Arrange - - // Act - imageLoader.invoke(testUris[0], previewSize, caching = true) - imageLoader.invoke(testUris[0], previewSize, caching = true) - advanceTimeBy(testJobTime) - runCurrent() - - // Assert - assertThat(fakeThumbnailLoader.invokeCalls).containsExactly(testUris[0]) - } -} diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt deleted file mode 100644 index d78e6665..00000000 --- a/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.contentpreview - -import android.content.ContentResolver -import android.graphics.Bitmap -import android.net.Uri -import android.util.Size -import com.google.common.truth.Truth.assertThat -import java.util.ArrayDeque -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit.MILLISECONDS -import java.util.concurrent.TimeUnit.SECONDS -import java.util.concurrent.atomic.AtomicInteger -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart.UNDISPATCHED -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Runnable -import kotlinx.coroutines.async -import kotlinx.coroutines.cancel -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Semaphore -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestCoroutineScheduler -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.yield -import org.junit.Assert.assertTrue -import org.junit.Test -import org.mockito.kotlin.any -import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doThrow -import org.mockito.kotlin.mock -import org.mockito.kotlin.never -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever - -@OptIn(ExperimentalCoroutinesApi::class) -class ImagePreviewImageLoaderTest { - private val imageSize = Size(300, 300) - private val uriOne = Uri.parse("content://org.package.app/image-1.png") - private val uriTwo = Uri.parse("content://org.package.app/image-2.png") - private val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - private val contentResolver = - mock<ContentResolver> { on { loadThumbnail(any(), any(), anyOrNull()) } doReturn bitmap } - private val scheduler = TestCoroutineScheduler() - private val dispatcher = UnconfinedTestDispatcher(scheduler) - private val scope = TestScope(dispatcher) - private val testSubject = - ImagePreviewImageLoader( - dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - private val previewSize = Size(500, 500) - - @Test - fun prePopulate_cachesImagesUpToTheCacheSize() = - scope.runTest { - testSubject.prePopulate(listOf(uriOne to previewSize, uriTwo to previewSize)) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - verify(contentResolver, never()).loadThumbnail(uriTwo, imageSize, null) - - testSubject(uriOne, previewSize) - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - } - - @Test - fun invoke_returnCachedImageWhenCalledTwice() = - scope.runTest { - testSubject(uriOne, previewSize) - testSubject(uriOne, previewSize) - - verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull()) - } - - @Test - fun invoke_whenInstructed_doesNotCache() = - scope.runTest { - testSubject(uriOne, previewSize, false) - testSubject(uriOne, previewSize, false) - - verify(contentResolver, times(2)).loadThumbnail(any(), any(), anyOrNull()) - } - - @Test - fun invoke_overlappedRequests_Deduplicate() = - scope.runTest { - val dispatcher = StandardTestDispatcher(scheduler) - val testSubject = - ImagePreviewImageLoader( - dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - coroutineScope { - launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } - launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } - scheduler.advanceUntilIdle() - } - - verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull()) - } - - @Test - fun invoke_oldRecordsEvictedFromTheCache() = - scope.runTest { - testSubject(uriOne, previewSize) - testSubject(uriTwo, previewSize) - testSubject(uriTwo, previewSize) - testSubject(uriOne, previewSize) - - verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) - verify(contentResolver, times(1)).loadThumbnail(uriTwo, imageSize, null) - } - - @Test - fun invoke_doNotCacheNulls() = - scope.runTest { - whenever(contentResolver.loadThumbnail(any(), any(), anyOrNull())).thenReturn(null) - testSubject(uriOne, previewSize) - testSubject(uriOne, previewSize) - - verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) - } - - @Test(expected = CancellationException::class) - fun invoke_onClosedImageLoaderScope_throwsCancellationException() = - scope.runTest { - val imageLoaderScope = CoroutineScope(coroutineContext) - val testSubject = - ImagePreviewImageLoader( - imageLoaderScope, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - imageLoaderScope.cancel() - testSubject(uriOne, previewSize) - } - - @Test(expected = CancellationException::class) - fun invoke_imageLoaderScopeClosedMidflight_throwsCancellationException() = - scope.runTest { - val dispatcher = StandardTestDispatcher(scheduler) - val imageLoaderScope = CoroutineScope(coroutineContext + dispatcher) - val testSubject = - ImagePreviewImageLoader( - imageLoaderScope, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - coroutineScope { - val deferred = - async(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } - imageLoaderScope.cancel() - scheduler.advanceUntilIdle() - deferred.await() - } - } - - @Test - fun invoke_multipleCallsWithDifferentCacheInstructions_cachingPrevails() = - scope.runTest { - val dispatcher = StandardTestDispatcher(scheduler) - val imageLoaderScope = CoroutineScope(coroutineContext + dispatcher) - val testSubject = - ImagePreviewImageLoader( - imageLoaderScope, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - coroutineScope { - launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } - launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, true) } - scheduler.advanceUntilIdle() - } - testSubject(uriOne, previewSize, true) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - } - - @Test - fun invoke_semaphoreGuardsContentResolverCalls() = - scope.runTest { - val contentResolver = - mock<ContentResolver> { - on { loadThumbnail(any(), any(), anyOrNull()) } doThrow - SecurityException("test") - } - val acquireCount = AtomicInteger() - val releaseCount = AtomicInteger() - val testSemaphore = - object : Semaphore { - override val availablePermits: Int - get() = error("Unexpected invocation") - - override suspend fun acquire() { - acquireCount.getAndIncrement() - } - - override fun tryAcquire(): Boolean { - error("Unexpected invocation") - } - - override fun release() { - releaseCount.getAndIncrement() - } - } - - val testSubject = - ImagePreviewImageLoader( - CoroutineScope(coroutineContext + dispatcher), - imageSize.width, - contentResolver, - cacheSize = 1, - testSemaphore, - ) - testSubject(uriOne, previewSize, false) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - assertThat(acquireCount.get()).isEqualTo(1) - assertThat(releaseCount.get()).isEqualTo(1) - } - - @Test - fun invoke_semaphoreIsReleasedAfterContentResolverFailure() = - scope.runTest { - val semaphoreDeferred = CompletableDeferred<Unit>() - val releaseCount = AtomicInteger() - val testSemaphore = - object : Semaphore { - override val availablePermits: Int - get() = error("Unexpected invocation") - - override suspend fun acquire() { - semaphoreDeferred.await() - } - - override fun tryAcquire(): Boolean { - error("Unexpected invocation") - } - - override fun release() { - releaseCount.getAndIncrement() - } - } - - val testSubject = - ImagePreviewImageLoader( - CoroutineScope(coroutineContext + dispatcher), - imageSize.width, - contentResolver, - cacheSize = 1, - testSemaphore, - ) - launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) } - - verify(contentResolver, never()).loadThumbnail(any(), any(), anyOrNull()) - - semaphoreDeferred.complete(Unit) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - assertThat(releaseCount.get()).isEqualTo(1) - } - - @Test - fun invoke_multipleSimultaneousCalls_limitOnNumberOfSimultaneousOutgoingCallsIsRespected() = - scope.runTest { - val requestCount = 4 - val thumbnailCallsCdl = CountDownLatch(requestCount) - val pendingThumbnailCalls = ArrayDeque<CountDownLatch>() - val contentResolver = - mock<ContentResolver> { - on { loadThumbnail(any(), any(), anyOrNull()) } doAnswer - { - val latch = CountDownLatch(1) - synchronized(pendingThumbnailCalls) { - pendingThumbnailCalls.offer(latch) - } - thumbnailCallsCdl.countDown() - assertTrue("Timeout waiting thumbnail calls", latch.await(1, SECONDS)) - bitmap - } - } - val name = "LoadImage" - val maxSimultaneousRequests = 2 - val threadsStartedCdl = CountDownLatch(requestCount) - val dispatcher = NewThreadDispatcher(name) { threadsStartedCdl.countDown() } - val testSubject = - ImagePreviewImageLoader( - CoroutineScope(coroutineContext + dispatcher + CoroutineName(name)), - imageSize.width, - contentResolver, - cacheSize = 1, - maxSimultaneousRequests, - ) - coroutineScope { - repeat(requestCount) { - launch { - testSubject(Uri.parse("content://org.pkg.app/image-$it.png"), previewSize) - } - } - yield() - // wait for all requests to be dispatched - assertThat(threadsStartedCdl.await(5, SECONDS)).isTrue() - - assertThat(thumbnailCallsCdl.await(100, MILLISECONDS)).isFalse() - synchronized(pendingThumbnailCalls) { - assertThat(pendingThumbnailCalls.size).isEqualTo(maxSimultaneousRequests) - } - - pendingThumbnailCalls.poll()?.countDown() - assertThat(thumbnailCallsCdl.await(100, MILLISECONDS)).isFalse() - synchronized(pendingThumbnailCalls) { - assertThat(pendingThumbnailCalls.size).isEqualTo(maxSimultaneousRequests) - } - - pendingThumbnailCalls.poll()?.countDown() - assertThat(thumbnailCallsCdl.await(100, MILLISECONDS)).isTrue() - synchronized(pendingThumbnailCalls) { - assertThat(pendingThumbnailCalls.size).isEqualTo(maxSimultaneousRequests) - } - for (cdl in pendingThumbnailCalls) { - cdl.countDown() - } - } - } -} - -private class NewThreadDispatcher( - private val coroutineName: String, - private val launchedCallback: () -> Unit -) : CoroutineDispatcher() { - override fun isDispatchNeeded(context: CoroutineContext): Boolean = true - - override fun dispatch(context: CoroutineContext, block: Runnable) { - Thread { - if (coroutineName == context[CoroutineName.Key]?.name) { - launchedCallback() - } - block.run() - } - .start() - } -} |