/* * 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.lemenzo.files.loaders import android.content.Context import android.os.Bundle import android.platform.test.annotations.EnableFlags import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import androidx.test.filters.SmallTest import com.lemenzo.files.ContentLock import com.lemenzo.files.DirectoryResult import com.lemenzo.files.base.DocumentInfo import com.lemenzo.files.flags.Flags.FLAG_USE_MATERIAL3 import com.lemenzo.files.flags.Flags.FLAG_USE_SEARCH_V2_READ_ONLY import com.lemenzo.files.rules.OverrideFlagsRule import com.lemenzo.files.testing.TestDocumentsProvider import com.lemenzo.files.testing.TestFileTypeLookup import com.lemenzo.files.testing.TestProvidersAccess import java.time.Duration import java.util.concurrent.CountDownLatch import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters import org.mockito.Mockito private const val TOTAL_FILE_COUNT = 10 @RunWith(Enclosed::class) class FolderLoaderTest() { @RunWith(Parameterized::class) @SmallTest class ParametrizedTests(private val testParams: LoaderTestParams) : BaseLoaderTest() { companion object { @JvmStatic @Parameters(name = "with parameters {0}") fun data() = listOf( LoaderTestParams( TOTAL_FILE_COUNT, "", null, ALL_RESULTS, Bundle(), TOTAL_FILE_COUNT ), // Result limiting only works for search, not folder navigation, expect limit to be ignored. LoaderTestParams(TOTAL_FILE_COUNT, "", null, 2, Bundle(), TOTAL_FILE_COUNT), // The first file is at NOW, the second at NOW - 1h, etc. LoaderTestParams( TOTAL_FILE_COUNT, "", Duration.ofMinutes(1L), ALL_RESULTS, Bundle(), 1 ), LoaderTestParams( TOTAL_FILE_COUNT, "", Duration.ofMinutes(60L + 1), ALL_RESULTS, Bundle(), 2 ), LoaderTestParams( TOTAL_FILE_COUNT, "", Duration.ofMinutes(TOTAL_FILE_COUNT * 60L + 1), ALL_RESULTS, Bundle(), TOTAL_FILE_COUNT ), ) } @get:Rule val setFlags = OverrideFlagsRule() val contentLock = ContentLock() lateinit var mockProvider: TestDocumentsProvider lateinit var queryOptions: QueryOptions @Before fun setUpTest() { queryOptions = QueryOptions( testParams.fakeFileCount + 1, testParams.maxResultsPerRoot, testParams.lastModifiedDelta, null, true, arrayOf("*/*"), testParams.otherArgs, ) // Set up sample files using Downloads provider. mockProvider = environment.mockProviders[TestProvidersAccess.DOWNLOADS.authority]!! val docs = createDocuments(TOTAL_FILE_COUNT) mockProvider.setNextChildDocumentsReturns(*docs) } @Test @EnableFlags(FLAG_USE_SEARCH_V2_READ_ONLY, FLAG_USE_MATERIAL3) fun testLoadInBackground() { // TODO(majewski): Is there a better way to create Downloads root folder DocumentInfo? val rootFolderInfo = DocumentInfo() rootFolderInfo.authority = TestProvidersAccess.DOWNLOADS.authority rootFolderInfo.userId = TestProvidersAccess.DOWNLOADS.userId val loader = FolderLoader( activity, TestFileTypeLookup(), contentLock, TestProvidersAccess.DOWNLOADS, rootFolderInfo, queryOptions, environment.state.sortModel ) val directoryResult = loader.loadInBackground() assertEquals(testParams.expectedCount, getFileCount(directoryResult)) } @Test @EnableFlags(FLAG_USE_SEARCH_V2_READ_ONLY, FLAG_USE_MATERIAL3) fun testListRootIfNullFolder() { val loader = FolderLoader( activity, TestFileTypeLookup(), contentLock, TestProvidersAccess.DOWNLOADS, null, queryOptions, environment.state.sortModel ) val directoryResult = loader.loadInBackground() assertEquals(testParams.expectedCount, getFileCount(directoryResult)) } } @SmallTest class PlainTests : BaseLoaderTest() { @get:Rule val setFlags = OverrideFlagsRule() val contentLock = ContentLock() lateinit var mockProvider: TestDocumentsProvider lateinit var queryOptions: QueryOptions lateinit var context: Context @Before fun setUpTest() { queryOptions = QueryOptions( TOTAL_FILE_COUNT, TOTAL_FILE_COUNT, null, null, true, arrayOf("*/*"), Bundle() ) // Set up sample files using Downloads provider. mockProvider = environment.mockProviders[TestProvidersAccess.DOWNLOADS.authority]!! val docs = createDocuments(TOTAL_FILE_COUNT) mockProvider.setNextChildDocumentsReturns(*docs) context = Mockito.mock(Context::class.java) } @Test @EnableFlags(FLAG_USE_SEARCH_V2_READ_ONLY, FLAG_USE_MATERIAL3) fun testThrownExceptions() { val message = "Testing exception throwing" mockProvider.setThrownRuntimeMessage(message) val loader = FolderLoader( activity, TestFileTypeLookup(), contentLock, TestProvidersAccess.DOWNLOADS, null, queryOptions, environment.state.sortModel ) val result = loader.loadInBackground() assertEquals(0, result?.cursor?.count) assertEquals(message, result?.exception?.message) } @Test @EnableFlags(FLAG_USE_SEARCH_V2_READ_ONLY, FLAG_USE_MATERIAL3) fun testCancelWhileLoading() { val doc1 = environment.model.createFile("downloads-file.txt") val doc2 = environment.model.createFile("pickles-file-1.txt") val doc3 = environment.model.createFile("pickles-file-2.txt") var result: DirectoryResult? = null // The latch that is released once the downloads provider starts its query. val downloadsStartedLoading = CountDownLatch(1) environment.mockProviders[TestProvidersAccess.DOWNLOADS.authority]?.apply { // Setting the latch that will be unlocked once the Downloads provider start // loading. setQueryDelayLatch(downloadsStartedLoading) // Setting the delay that ensures that the downloads provider still is loading // while we trigger a new loader creation and thus cancellation of the old one. setQueryDelay(500L) setNextChildDocumentsReturns(doc1) } environment.mockProviders[TestProvidersAccess.PICKLES.authority]?.apply { setNextChildDocumentsReturns(doc2, doc3) } // This latch is released once the results are delivered to the loader. val resultsDelivered = CountDownLatch(1) val loaderCallbacks: LoaderManager.LoaderCallbacks = object : LoaderManager.LoaderCallbacks { var runCount = 0 override fun onCreateLoader(id: Int, args: Bundle?): Loader { val rootInfo = if (++runCount == 1) { TestProvidersAccess.DOWNLOADS } else { TestProvidersAccess.PICKLES } val rootFolderInfo = DocumentInfo() rootFolderInfo.authority = rootInfo.authority rootFolderInfo.userId = rootInfo.userId return FolderLoader( activity, TestFileTypeLookup(), contentLock, rootInfo, rootFolderInfo, queryOptions, environment.state.sortModel, ) } override fun onLoadFinished( loader: Loader, data: DirectoryResult?, ) { result = data resultsDelivered.countDown() } override fun onLoaderReset(loader: Loader) { loader.reset() } } // Effectively calls `onCreateLoader` and then runs it by calling `startLoading()`. activity.supportLoaderManager.restartLoader(1, null, loaderCallbacks).startLoading() downloadsStartedLoading.await() // Effectively calls `onCreateLoader` the second time, causing the first loader to // be cancelled. Then runs the new loader by calling `startLoading()`. activity.supportLoaderManager.restartLoader(1, null, loaderCallbacks).startLoading() resultsDelivered.await() // We use the fact that pickles returns 2 returns to verify we only got pickles results. assertNotNull(result) assertEquals(2, getFileCount(result)) val resultSet = getDocuments(result).map { it.displayName }.toSet() assertEquals(setOf(doc2.displayName, doc3.displayName), resultSet) } } }