Add test to catch subsequent scans of nomedia directories
Directories with .nomedia files should only be scanned once (unless the
parent directory is moved). We do not scan directories with .nomedia
file if the string in .nomedia file matches the file path of the
parents directory (this is usually set in the first scan).
Since we do not have strong benchmarks to compare these numbers with,
we are using Timers to compare numbers relatively.
Extracted Timer logic to a utils file for easy access by all tests.
Bug: 173156814
Test: atest ModernMediaScannerTest
Test: atest PerformanceTest
Change-Id: I2f83721ef0ec2bc92f80c11c8a7e7ffc73123723
diff --git a/tests/Android.bp b/tests/Android.bp
index cab61cf..8054a05 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -17,9 +17,11 @@
"main_res",
"res",
],
+
srcs: [
":framework-mediaprovider-sources",
":mediaprovider-sources",
+ ":mediaprovider-testutils",
"src/**/*.java",
],
@@ -50,3 +52,8 @@
],
},
}
+
+filegroup {
+ name: "mediaprovider-testutils",
+ srcs: ["utils/**/*.java"],
+}
diff --git a/tests/client/Android.bp b/tests/client/Android.bp
index 26a0a0b..2b35634 100644
--- a/tests/client/Android.bp
+++ b/tests/client/Android.bp
@@ -10,6 +10,7 @@
srcs: [
"src/**/*.java",
+ ":mediaprovider-testutils",
],
libs: [
diff --git a/tests/client/src/com/android/providers/media/client/PerformanceTest.java b/tests/client/src/com/android/providers/media/client/PerformanceTest.java
index 21cdb8b..867d459 100644
--- a/tests/client/src/com/android/providers/media/client/PerformanceTest.java
+++ b/tests/client/src/com/android/providers/media/client/PerformanceTest.java
@@ -36,6 +36,8 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.providers.media.tests.utils.Timer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -281,6 +283,7 @@
renameFilesTimer.dumpResults();
deleteTimer.dumpResults();
}
+
private void doDirOperations(int size, Timer createTimer, Timer readTimer,
Timer renameDirTimer, Timer renameFilesTimer, Timer deleteTimer) throws Exception {
createTimer.start();
@@ -337,52 +340,6 @@
return new HashSet<>(uris);
}
- /**
- * Timer that can be started/stopped with nanosecond accuracy, and later
- * averaged based on the number of times it was cycled.
- */
- static class Timer {
- private final String name;
- private int count;
- private long duration;
- private long start;
-
- public Timer(String name) {
- this.name = name;
- }
-
- public void start() {
- if (start != 0) {
- throw new IllegalStateException();
- } else {
- start = SystemClock.elapsedRealtimeNanos();
- }
- }
-
- public void stop() {
- if (start == 0) {
- throw new IllegalStateException();
- } else {
- duration += (SystemClock.elapsedRealtimeNanos() - start);
- start = 0;
- count++;
- }
- }
-
- public long getAverageDurationMillis() {
- return TimeUnit.MILLISECONDS.convert(duration / count, TimeUnit.NANOSECONDS);
- }
-
- public void dumpResults() {
- final long duration = getAverageDurationMillis();
- Log.v(TAG, name + ": " + duration + "ms");
-
- final Bundle results = new Bundle();
- results.putLong(name, duration);
- InstrumentationRegistry.getInstrumentation().sendStatus(0, results);
- }
- }
-
private static class Timers {
public final Timer actionInsert = new Timer("action_insert");
public final Timer actionUpdate = new Timer("action_update");
diff --git a/tests/client/src/com/android/providers/media/client/PlaylistPerformanceTest.java b/tests/client/src/com/android/providers/media/client/PlaylistPerformanceTest.java
index 5df8423..c4ea8e8 100644
--- a/tests/client/src/com/android/providers/media/client/PlaylistPerformanceTest.java
+++ b/tests/client/src/com/android/providers/media/client/PlaylistPerformanceTest.java
@@ -37,7 +37,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.providers.media.client.PerformanceTest.Timer;
+import com.android.providers.media.tests.utils.Timer;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index 9b7f0d2..1d21231 100644
--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
@@ -36,6 +36,8 @@
import static com.android.providers.media.util.FileUtils.isDirectoryHidden;
import static com.android.providers.media.util.FileUtils.isFileHidden;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -68,6 +70,7 @@
import com.android.providers.media.R;
import com.android.providers.media.scan.MediaScannerTest.IsolatedContext;
+import com.android.providers.media.tests.utils.Timer;
import com.android.providers.media.util.FileUtils;
import com.google.common.io.ByteStreams;
@@ -90,6 +93,11 @@
// TODO: scan directory-vs-files and confirm identical results
private static final String TAG = "ModernMediaScannerTest";
+ /**
+ * Number of times we should repeat an operation to get an average/max.
+ */
+ private static final int COUNT_REPEAT = 5;
+
private File mDir;
private Context mIsolatedContext;
@@ -1039,12 +1047,56 @@
mModern.scanDirectory(mDir, REASON_UNKNOWN);
- try (Cursor cursor = mIsolatedResolver
- .query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
- new String[] { MediaColumns.XMP }, null, null, null)) {
- assertEquals(1, cursor.getCount());
- cursor.moveToFirst();
- assertEquals(0, cursor.getBlob(0).length);
+ try (Cursor cursor = mIsolatedResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ new String[] { MediaColumns.XMP }, null, null, null)) {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(0, cursor.getBlob(0).length);
}
}
+
+ @Test
+ public void testNoOpScan_NoMediaDirs() throws Exception {
+ File nomedia = new File(mDir, ".nomedia");
+ assertThat(nomedia.createNewFile()).isTrue();
+ for (int i = 0; i < 100; i++) {
+ File file = new File(mDir, "file_" + System.nanoTime());
+ assertThat(file.createNewFile()).isTrue();
+ }
+ Timer firstDirScan = new Timer("firstDirScan");
+ firstDirScan.start();
+ // Time taken : preVisitDirectory + 100 visitFiles
+ mModern.scanDirectory(mDir, REASON_UNKNOWN);
+ firstDirScan.stop();
+ firstDirScan.dumpResults();
+
+ // Time taken : preVisitDirectory
+ Timer noOpDirScan = new Timer("noOpDirScan");
+ for (int i = 0 ; i < COUNT_REPEAT ; i++) {
+ noOpDirScan.start();
+ mModern.scanDirectory(mDir, REASON_UNKNOWN);
+ noOpDirScan.stop();
+ }
+ noOpDirScan.dumpResults();
+ assertThat(noOpDirScan.getMaxDurationMillis()).isLessThan(
+ firstDirScan.getMaxDurationMillis());
+
+ // renaming directory for non-M_E_S apps does a scan of the directory as well;
+ // so subsequent scans should be noOp as the directory is not dirty.
+ File renamedTestDir = new File(mIsolatedContext.getExternalMediaDirs()[0],
+ "renamed_test_" + System.nanoTime());
+ assertThat(mDir.renameTo(renamedTestDir)).isTrue();
+
+ Timer renamedDirScan = new Timer("renamedDirScan");
+ renamedDirScan.start();
+ // Time taken : preVisitDirectory
+ mModern.scanDirectory(renamedTestDir, REASON_UNKNOWN);
+ renamedDirScan.stop();
+ renamedDirScan.dumpResults();
+ assertThat(renamedDirScan.getMaxDurationMillis()).isLessThan(
+ firstDirScan.getMaxDurationMillis());
+
+ // This is essential for folder cleanup in tearDown
+ mDir = renamedTestDir;
+ }
}
diff --git a/tests/utils/src/com/android/providers/media/tests/utils/Timer.java b/tests/utils/src/com/android/providers/media/tests/utils/Timer.java
new file mode 100644
index 0000000..6d8caca
--- /dev/null
+++ b/tests/utils/src/com/android/providers/media/tests/utils/Timer.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (C) 2020 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.providers.media.tests.utils;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * General Timer function for MediaProvider tests.
+ */
+public class Timer {
+ private static final String TAG = "Timer";
+
+ private final String name;
+ private int count;
+ private long duration;
+ private long start;
+ private long maxRunDuration = 0;
+
+ public Timer(String name) {
+ this.name = name;
+ }
+
+ public void start() {
+ if (start != 0) {
+ throw new IllegalStateException();
+ } else {
+ start = SystemClock.elapsedRealtimeNanos();
+ }
+ }
+
+ public void stop() {
+ long currentRunDuration = 0;
+ if (start == 0) {
+ throw new IllegalStateException();
+ } else {
+ currentRunDuration = (SystemClock.elapsedRealtimeNanos() - start);
+ maxRunDuration = (currentRunDuration > maxRunDuration) ? currentRunDuration :
+ maxRunDuration;
+ duration += currentRunDuration;
+ start = 0;
+ count++;
+ }
+ }
+
+ public long getAverageDurationMillis() {
+ return TimeUnit.MILLISECONDS.convert(duration / count, TimeUnit.NANOSECONDS);
+ }
+
+ public long getMaxDurationMillis() {
+ return TimeUnit.MILLISECONDS.convert(maxRunDuration, TimeUnit.NANOSECONDS);
+ }
+
+ public void dumpResults() {
+ final long duration = getAverageDurationMillis();
+ Log.v(TAG, name + ": " + duration + "ms");
+
+ final Bundle results = new Bundle();
+ results.putLong(name, duration);
+ InstrumentationRegistry.getInstrumentation().sendStatus(0, results);
+ }
+}