Add unit tests for recent regressions
Added three tests to catch photos editing issue and similar
regressions from app compatibility testing.
TestUpsert already tests one of the bug.
Test: atest
FuseDaemonHostTest#testCreateAndRenameDoesntLeaveStaleDBRow_hasRW
Test: atest FuseDaemonHostTest#testRenameDoesntInvalidateUri_hasRW
Test: atest FuseDaemonHostTest#testCanRenameAFileWithNoDBRow_hasRW
Bug: 150579385
Change-Id: I166348c17deb924ad42d9b0e198073b527e2ff51
diff --git a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java
index 852ab0e..9ebdfe8 100644
--- a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java
+++ b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java
@@ -195,4 +195,20 @@
public void testLegacyAppCanOwnAFile_hasW() throws Exception {
runDeviceTest("testLegacyAppCanOwnAFile_hasW");
}
+
+ @Test
+ public void testCreateAndRenameDoesntLeaveStaleDBRow_hasRW() throws Exception {
+ runDeviceTest("testCreateAndRenameDoesntLeaveStaleDBRow_hasRW");
+ }
+
+ @Test
+ public void testRenameDoesntInvalidateUri_hasRW() throws Exception {
+ runDeviceTest("testRenameDoesntInvalidateUri_hasRW");
+ }
+
+
+ @Test
+ public void testCanRenameAFileWithNoDBRow_hasRW() throws Exception {
+ runDeviceTest("testCanRenameAFileWithNoDBRow_hasRW");
+ }
}
diff --git a/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java b/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
index 8dfbb4d..b28f7fc 100644
--- a/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
+++ b/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
@@ -16,13 +16,17 @@
package com.android.tests.fused.legacy;
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA1;
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA2;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA1;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA2;
import static com.android.tests.fused.lib.TestUtils.assertCanRenameFile;
import static com.android.tests.fused.lib.TestUtils.assertCanRenameDirectory;
import static com.android.tests.fused.lib.TestUtils.assertCantRenameFile;
+import static com.android.tests.fused.lib.TestUtils.assertFileContent;
import static com.android.tests.fused.lib.TestUtils.createFileAs;
-
-
import static com.android.tests.fused.lib.TestUtils.deleteFileAsNoThrow;
+import static com.android.tests.fused.lib.TestUtils.getContentResolver;
import static com.android.tests.fused.lib.TestUtils.getFileOwnerPackageFromDatabase;
import static com.android.tests.fused.lib.TestUtils.getFileRowIdFromDatabase;
import static com.android.tests.fused.lib.TestUtils.installApp;
@@ -36,11 +40,17 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.Manifest;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Environment;
+import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -52,6 +62,8 @@
import com.android.cts.install.lib.TestApp;
import com.android.tests.fused.lib.ReaddirTestHelper;
+import com.google.common.io.Files;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -59,7 +71,9 @@
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -76,6 +90,7 @@
private static final String TAG = "LegacyFileAccessTest";
static final String THIS_PACKAGE_NAME = InstrumentationRegistry.getContext().getPackageName();
+ static final String IMAGE_FILE_NAME = "FilePathAccessTest_file.jpg";
static final String VIDEO_FILE_NAME = "LegacyAccessTest_file.mp4";
static final String NONMEDIA_FILE_NAME = "LegacyAccessTest_file.pdf";
@@ -460,6 +475,134 @@
}
}
+ /**
+ * b/14966134: Test that FuseDaemon doesn't leave stale database entries after create() and
+ * rename().
+ */
+ @Test
+ public void testCreateAndRenameDoesntLeaveStaleDBRow_hasRW() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+ final File directoryDCIM = new File(Environment.getExternalStorageDirectory(),
+ Environment.DIRECTORY_DCIM);
+ final File videoFile = new File(directoryDCIM, VIDEO_FILE_NAME);
+ final File renamedVideoFile = new File(directoryDCIM, "Renamed_" + VIDEO_FILE_NAME);
+ final ContentResolver cr = getContentResolver();
+
+ try {
+ assertThat(videoFile.createNewFile()).isTrue();
+ assertThat(videoFile.renameTo(renamedVideoFile)).isTrue();
+
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, renamedVideoFile.getAbsolutePath());
+ // Insert new renamedVideoFile to database
+ final Uri uri = cr.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values, null);
+ assertNotNull(uri);
+
+ // Query for all images/videos in the device.
+ // This shouldn't list videoFile which was renamed to renamedVideoFile.
+ final ArrayList<String> imageAndVideoFiles = getImageAndVideoFilesFromDatabase();
+ assertThat(imageAndVideoFiles).contains(renamedVideoFile.getName());
+ assertThat(imageAndVideoFiles).doesNotContain(videoFile.getName());
+ } finally {
+ videoFile.delete();
+ renamedVideoFile.delete();
+ MediaStore.scanFile(cr, renamedVideoFile);
+ }
+ }
+
+ /**
+ * b/150147690,b/150193381: Test that file rename doesn't delete any existing Uri.
+ */
+ @Test
+ public void testRenameDoesntInvalidateUri_hasRW() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+ final File directoryDCIM = new File(Environment.getExternalStorageDirectory(),
+ Environment.DIRECTORY_DCIM);
+ final File imageFile = new File(directoryDCIM, IMAGE_FILE_NAME);
+ final File temporaryImageFile = new File(directoryDCIM, IMAGE_FILE_NAME + "_.tmp");
+ final ContentResolver cr = getContentResolver();
+
+ try {
+ assertThat(imageFile.createNewFile()).isTrue();
+ try (final FileOutputStream fos = new FileOutputStream(imageFile)) {
+ fos.write(BYTES_DATA1);
+ }
+ // Insert this file to database.
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, imageFile.getAbsolutePath());
+ final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
+ assertNotNull(uri);
+
+ Files.copy(imageFile, temporaryImageFile);
+ // Write more bytes to temporaryImageFile
+ try (final FileOutputStream fos = new FileOutputStream(temporaryImageFile, true)) {
+ fos.write(BYTES_DATA2);
+ }
+ assertThat(imageFile.delete()).isTrue();
+ temporaryImageFile.renameTo(imageFile);
+
+ // Previous uri of imageFile is unaltered after delete & rename.
+ final Uri scannedUri = MediaStore.scanFile(cr, imageFile);
+ assertThat(scannedUri.getLastPathSegment()).isEqualTo(uri.getLastPathSegment());
+
+ final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes();
+ assertFileContent(imageFile, expected);
+ } finally {
+ imageFile.delete();
+ temporaryImageFile.delete();
+ MediaStore.scanFile(cr, imageFile);
+ }
+ }
+
+ /**
+ * b/150498564,b/150274099: Test that apps can rename files that are not in database.
+ */
+ @Test
+ public void testCanRenameAFileWithNoDBRow_hasRW() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+ final File directoryDCIM = new File(Environment.getExternalStorageDirectory(),
+ Environment.DIRECTORY_DCIM);
+ final File directoryNoMedia = new File(directoryDCIM, ".directoryNoMedia");
+ final File imageInNoMediaDir = new File(directoryNoMedia, IMAGE_FILE_NAME);
+ final File renamedImageInDCIM = new File(directoryDCIM, IMAGE_FILE_NAME);
+ final File noMediaFile = new File(directoryNoMedia, ".nomedia");
+ final ContentResolver cr = getContentResolver();
+
+ try {
+ if (!directoryNoMedia.exists()) {
+ assertThat(directoryNoMedia.mkdirs()).isTrue();
+ }
+ assertThat(noMediaFile.createNewFile()).isTrue();
+ assertThat(imageInNoMediaDir.createNewFile()).isTrue();
+ // Remove imageInNoMediaDir from database.
+ MediaStore.scanFile(cr, directoryNoMedia);
+
+ // Query for all images/videos in the device. This shouldn't list imageInNoMediaDir
+ assertThat(getImageAndVideoFilesFromDatabase())
+ .doesNotContain(imageInNoMediaDir.getName());
+
+ // Rename shouldn't throw error even if imageInNoMediaDir is not in database.
+ assertThat(imageInNoMediaDir.renameTo(renamedImageInDCIM)).isTrue();
+ // We can insert renamedImageInDCIM to database
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, renamedImageInDCIM.getAbsolutePath());
+ final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
+ assertNotNull(uri);
+ } finally {
+ imageInNoMediaDir.delete();
+ renamedImageInDCIM.delete();
+ MediaStore.scanFile(cr, renamedImageInDCIM);
+ noMediaFile.delete();
+ }
+
+ }
+
private static void assertCanCreateFile(File file) throws IOException {
if (file.exists()) {
file.delete();
@@ -489,4 +632,27 @@
dir.delete();
}
}
+
+ /**
+ * Queries {@link ContentResolver} for all image and video files, returns display name of
+ * corresponding files.
+ */
+ private static ArrayList<String> getImageAndVideoFilesFromDatabase() {
+ ArrayList<String> mediaFiles = new ArrayList<>();
+ final String selection = "is_pending = 0 AND is_trashed = 0 AND "
+ + "(media_type = ? OR media_type = ?)";
+ final String[] selectionArgs = new String[] {
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
+ String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)};
+
+ try (Cursor c = getContentResolver().query(
+ MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
+ /* projection */ new String[]{MediaStore.MediaColumns.DISPLAY_NAME},
+ selection, selectionArgs, null)) {
+ while (c.moveToNext()) {
+ mediaFiles.add(c.getString(0));
+ }
+ }
+ return mediaFiles;
+ }
}
diff --git a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
index 89863ed..75735a5 100644
--- a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
+++ b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
@@ -43,7 +43,9 @@
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.provider.MediaStore;
+import android.system.ErrnoException;
import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -58,7 +60,10 @@
import com.google.common.io.ByteStreams;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
@@ -76,6 +81,13 @@
public static final String CREATE_FILE_QUERY = "com.android.tests.fused.createfile";
public static final String DELETE_FILE_QUERY = "com.android.tests.fused.deletefile";
+
+ public static final String STR_DATA1 = "Just some random text";
+ public static final String STR_DATA2 = "More arbitrary stuff";
+
+ public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
+ public static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
+
private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
private static final long POLLING_SLEEP_MILLIS = 100;
@@ -467,6 +479,32 @@
}
/**
+ * Asserts the entire content of the file equals exactly {@code expectedContent}.
+ */
+ public static void assertFileContent(File file, byte[] expectedContent) throws IOException {
+ try (final FileInputStream fis = new FileInputStream(file)) {
+ assertInputStreamContent(fis, expectedContent);
+ }
+ }
+
+ /**
+ * Asserts the entire content of the file equals exactly {@code expectedContent}.
+ * <p>Sets {@code fd} to beginning of file first.
+ */
+ public static void assertFileContent(FileDescriptor fd, byte[] expectedContent)
+ throws IOException, ErrnoException {
+ Os.lseek(fd, 0, OsConstants.SEEK_SET);
+ try (final FileInputStream fis = new FileInputStream(fd)) {
+ assertInputStreamContent(fis, expectedContent);
+ }
+ }
+
+ private static void assertInputStreamContent(InputStream in, byte[] expectedContent)
+ throws IOException {
+ assertThat(ByteStreams.toByteArray(in)).isEqualTo(expectedContent);
+ }
+
+ /**
* Checks if the given {@code permission} is granted and corresponding AppOp is MODE_ALLOWED.
*/
private static boolean checkPermissionAndAppOp(String permission) {
diff --git a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
index a17e7b9..40ffba7 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -25,12 +25,17 @@
import static com.android.tests.fused.lib.RedactionTestHelper.assertExifMetadataMismatch;
import static com.android.tests.fused.lib.RedactionTestHelper.getExifMetadata;
import static com.android.tests.fused.lib.RedactionTestHelper.getExifMetadataFromRawResource;
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA1;
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA2;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA1;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA2;
import static com.android.tests.fused.lib.TestUtils.assertCanRenameFile;
import static com.android.tests.fused.lib.TestUtils.assertCanRenameDirectory;
import static com.android.tests.fused.lib.TestUtils.adoptShellPermissionIdentity;
import static com.android.tests.fused.lib.TestUtils.allowAppOpsToUid;
import static com.android.tests.fused.lib.TestUtils.assertCantRenameDirectory;
import static com.android.tests.fused.lib.TestUtils.assertCantRenameFile;
+import static com.android.tests.fused.lib.TestUtils.assertFileContent;
import static com.android.tests.fused.lib.TestUtils.assertThrows;
import static com.android.tests.fused.lib.TestUtils.createFileAs;
import static com.android.tests.fused.lib.TestUtils.deleteFileAs;
@@ -79,8 +84,6 @@
import com.android.cts.install.lib.TestApp;
import com.android.tests.fused.lib.ReaddirTestHelper;
-import com.google.common.io.ByteStreams;
-
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -123,12 +126,6 @@
static final String IMAGE_FILE_NAME = "FilePathAccessTest_file.jpg";
static final String NONMEDIA_FILE_NAME = "FilePathAccessTest_file.pdf";
- static final String STR_DATA1 = "Just some random text";
- static final String STR_DATA2 = "More arbitrary stuff";
-
- static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
- static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
-
static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
private static final TestApp TEST_APP_A = new TestApp("TestAppA",
@@ -1591,30 +1588,4 @@
+ "running the test!");
}
}
-
- /**
- * Asserts the entire content of the file equals exactly {@code expectedContent}.
- */
- private static void assertFileContent(File file, byte[] expectedContent) throws IOException {
- try (final FileInputStream fis = new FileInputStream(file)) {
- assertInputStreamContent(fis, expectedContent);
- }
- }
-
- /**
- * Asserts the entire content of the file equals exactly {@code expectedContent}.
- * <p>Sets {@code fd} to beginning of file first.
- */
- private static void assertFileContent(FileDescriptor fd, byte[] expectedContent)
- throws IOException, ErrnoException {
- Os.lseek(fd, 0, OsConstants.SEEK_SET);
- try (final FileInputStream fis = new FileInputStream(fd)) {
- assertInputStreamContent(fis, expectedContent);
- }
- }
-
- private static void assertInputStreamContent(InputStream in, byte[] expectedContent)
- throws IOException {
- assertThat(ByteStreams.toByteArray(in)).isEqualTo(expectedContent);
- }
}