Merge "[ST02] Add methods for synthesizing DNS packets"
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
index 67e9c93..2534d61 100644
--- a/staticlibs/device/com/android/net/module/util/BpfDump.java
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -15,13 +15,17 @@
*/
package com.android.net.module.util;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Base64;
import android.util.Pair;
import androidx.annotation.NonNull;
+import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.function.BiFunction;
/**
* The classes and the methods for BPF dump utilization.
@@ -74,6 +78,38 @@
return new Pair<>(k, v);
}
+ /**
+ * Dump the BpfMap name and entries
+ */
+ public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
+ PrintWriter pw, String mapName, BiFunction<K, V, String> entryToString) {
+ dumpMap(map, pw, mapName, "" /* header */, entryToString);
+ }
+
+ /**
+ * Dump the BpfMap name, header, and entries
+ */
+ public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
+ PrintWriter pw, String mapName, String header, BiFunction<K, V, String> entryToString) {
+ pw.println(mapName + ":");
+ if (!header.isEmpty()) {
+ pw.println(" " + header);
+ }
+ try {
+ map.forEach((key, value) -> {
+ // Value could be null if there is a concurrent entry deletion.
+ // http://b/220084230.
+ if (value != null) {
+ pw.println(" " + entryToString.apply(key, value));
+ } else {
+ pw.println("Entry is deleted while dumping, iterating from first entry");
+ }
+ });
+ } catch (ErrnoException e) {
+ pw.println("Map dump end with error: " + Os.strerror(e.errno));
+ }
+ }
+
// TODO: add a helper to dump bpf map content with the map name, the header line
// (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that
// knows how to dump each line, and the PrintWriter.
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index f7019a5..2267f31 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -24,9 +24,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.net.module.util.Struct;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -104,22 +101,6 @@
mValueSize = Struct.getSize(value);
}
- /**
- * Constructor for testing only.
- * The derived class implements an internal mocked map. It need to implement all functions
- * which are related with the native BPF map because the BPF map handler is not initialized.
- * See BpfCoordinatorTest#TestBpfMap.
- * TODO: remove once TestBpfMap derive from IBpfMap.
- */
- @VisibleForTesting
- protected BpfMap(final Class<K> key, final Class<V> value) {
- mMapFd = null; // unused
- mKeyClass = key;
- mValueClass = value;
- mKeySize = Struct.getSize(key);
- mValueSize = Struct.getSize(value);
- }
-
/**
* Update an existing or create a new key -> value entry in an eBbpf map.
* (use insertOrReplaceEntry() if you need to know whether insert or replace happened)
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 30a1c33..f8f250d 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -259,4 +259,17 @@
return defaultValue;
}
}
+
+ /**
+ * Gets int config from resources.
+ */
+ public static int getResIntegerConfig(@NonNull final Context context,
+ @BoolRes int configResource, final int defaultValue) {
+ final Resources res = context.getResources();
+ try {
+ return res.getInteger(configResource);
+ } catch (Resources.NotFoundException e) {
+ return defaultValue;
+ }
+ }
}
diff --git a/staticlibs/device/com/android/net/module/util/IBpfMap.java b/staticlibs/device/com/android/net/module/util/IBpfMap.java
index dce369a..83ff875 100644
--- a/staticlibs/device/com/android/net/module/util/IBpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/IBpfMap.java
@@ -19,6 +19,7 @@
import androidx.annotation.NonNull;
+import java.io.IOException;
import java.util.NoSuchElementException;
/**
@@ -74,4 +75,8 @@
/** Clears the map. */
void clear() throws ErrnoException;
+
+ /** Close for AutoCloseable. */
+ @Override
+ void close() throws IOException;
}
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index d1728c2..7cac90d 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -99,7 +99,8 @@
* @return The index of the first element that matches the predicate, or -1 if none.
*/
@Nullable
- public static <T> int indexOf(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) {
+ public static <T> int indexOf(@NonNull final Collection<T> elem,
+ @NonNull final Predicate<? super T> predicate) {
int idx = 0;
for (final T e : elem) {
if (predicate.test(e)) return idx;
@@ -222,7 +223,8 @@
* @param <T> type of elements
* @return true if |haystack| contains any of the |needles|, false otherwise
*/
- public static <T> boolean containsAny(Collection<T> haystack, Collection<? extends T> needles) {
+ public static <T> boolean containsAny(@NonNull final Collection<T> haystack,
+ @NonNull final Collection<? extends T> needles) {
for (T needle : needles) {
if (haystack.contains(needle)) return true;
}
@@ -236,7 +238,8 @@
* @param <T> type of elements
* @return true if |haystack| contains all of the |needles|, false otherwise
*/
- public static <T> boolean containsAll(Collection<T> haystack, Collection<? extends T> needles) {
+ public static <T> boolean containsAll(@NonNull final Collection<T> haystack,
+ @NonNull final Collection<? extends T> needles) {
return haystack.containsAll(needles);
}
@@ -248,7 +251,8 @@
* @return The first element matching the predicate, or null if none.
*/
@Nullable
- public static <T> T findFirst(Collection<T> haystack, Predicate<? super T> condition) {
+ public static <T> T findFirst(@NonNull final Collection<T> haystack,
+ @NonNull final Predicate<? super T> condition) {
for (T needle : haystack) {
if (condition.test(needle)) return needle;
}
@@ -267,11 +271,24 @@
// wasteful (store and reverse a copy, test all elements, or recurse to the end of the
// list to test on the up path and possibly blow the call stack)
@Nullable
- public static <T> T findLast(List<T> haystack, Predicate<? super T> condition) {
+ public static <T> T findLast(@NonNull final List<T> haystack,
+ @NonNull final Predicate<? super T> condition) {
for (int i = haystack.size() - 1; i >= 0; --i) {
final T needle = haystack.get(i);
if (condition.test(needle)) return needle;
}
return null;
}
+
+ /**
+ * Returns whether a collection contains an element matching a condition
+ * @param haystack The collection to search.
+ * @param condition The predicate to match.
+ * @param <T> The type of element in the collection.
+ * @return Whether the collection contains any element matching the condition.
+ */
+ public static <T> boolean contains(@NonNull final Collection<T> haystack,
+ @NonNull final Predicate<? super T> condition) {
+ return -1 != indexOf(haystack, condition);
+ }
}
diff --git a/staticlibs/netd/libnetdutils/Syscalls.cpp b/staticlibs/netd/libnetdutils/Syscalls.cpp
index 9f653f7..7e1a242 100644
--- a/staticlibs/netd/libnetdutils/Syscalls.cpp
+++ b/staticlibs/netd/libnetdutils/Syscalls.cpp
@@ -26,7 +26,7 @@
// Retry syscall fn as long as it returns -1 with errno == EINTR
template <typename FnT, typename... Params>
-typename std::result_of<FnT(Params...)>::type syscallRetry(FnT fn, Params&&... params) {
+typename std::invoke_result<FnT, Params...>::type syscallRetry(FnT fn, Params&&... params) {
auto rv = fn(std::forward<Params>(params)...);
while ((rv == -1) && (errno == EINTR)) {
rv = fn(std::forward<Params>(params)...);
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
index 395011c..3932925 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
@@ -18,15 +18,21 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import android.util.Pair;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.TestBpfMap;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class BpfDumpTest {
@@ -79,4 +85,37 @@
assertThrowsIllegalArgumentException(
TEST_KEY_VAL_BASE64 + BASE64_DELIMITER + TEST_KEY_BASE64);
}
+
+ private String getDumpMap(final IBpfMap<Struct.U32, Struct.U32> map) {
+ final StringWriter sw = new StringWriter();
+ BpfDump.dumpMap(map, new PrintWriter(sw), "mapName", "header",
+ (key, val) -> "key=" + key.val + ", val=" + val.val);
+ return sw.toString();
+ }
+
+ @Test
+ public void testDumpMap() throws Exception {
+ final IBpfMap<Struct.U32, Struct.U32> map =
+ new TestBpfMap<>(Struct.U32.class, Struct.U32.class);
+ map.updateEntry(new Struct.U32(123), new Struct.U32(456));
+
+ final String dump = getDumpMap(map);
+ assertEquals(dump, "mapName:\n"
+ + " header\n"
+ + " key=123, val=456\n");
+ }
+
+ @Test
+ public void testDumpMapMultipleEntries() throws Exception {
+ final IBpfMap<Struct.U32, Struct.U32> map =
+ new TestBpfMap<>(Struct.U32.class, Struct.U32.class);
+ map.updateEntry(new Struct.U32(123), new Struct.U32(456));
+ map.updateEntry(new Struct.U32(789), new Struct.U32(123));
+
+ final String dump = getDumpMap(map);
+ assertTrue(dump.contains("mapName:"));
+ assertTrue(dump.contains("header"));
+ assertTrue(dump.contains("key=123, val=456"));
+ assertTrue(dump.contains("key=789, val=123"));
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index 0991352..0f00d0b 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -94,6 +94,12 @@
assertTrue(CollectionUtils.contains(arrayOf("A", "B", "C"), "C"))
assertFalse(CollectionUtils.contains(arrayOf("A", "B", "C"), "D"))
assertFalse(CollectionUtils.contains(null, "A"))
+
+ val list = listOf("A", "B", "Ab", "C", "D", "E", "A", "E")
+ assertTrue(CollectionUtils.contains(list) { it.length == 2 })
+ assertFalse(CollectionUtils.contains(list) { it.length < 1 })
+ assertTrue(CollectionUtils.contains(list) { it > "A" })
+ assertFalse(CollectionUtils.contains(list) { it > "F" })
}
@Test
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index c65f793..a8e7993 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -283,4 +283,13 @@
doThrow(new Resources.NotFoundException()).when(mResources).getBoolean(someResId);
assertFalse(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false));
}
+
+ @Test
+ public void testGetResIntegerConfig() {
+ final int someResId = 1234;
+ doReturn(2097).when(mResources).getInteger(someResId);
+ assertEquals(2097, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098));
+ doThrow(new Resources.NotFoundException()).when(mResources).getInteger(someResId);
+ assertEquals(2098, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098));
+ }
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt
index 98464eb..cbdc017 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt
@@ -2,7 +2,6 @@
import android.os.SystemClock
import java.util.concurrent.CyclicBarrier
-import kotlin.system.measureTimeMillis
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertNull
@@ -17,14 +16,18 @@
val INTERPRET_TIME_UNIT = 40L // ms
/**
- * A small interpreter for testing parallel code. The interpreter will read a list of lines
- * consisting of "|"-separated statements. Each column runs in a different concurrent thread
- * and all threads wait for each other in between lines. Each statement is split on ";" then
- * matched with regular expressions in the instructionTable constant, which contains the
- * code associated with each statement. The interpreter supports an object being passed to
- * the interpretTestSpec() method to be passed in each lambda (think about the object under
- * test), and an optional transform function to be executed on the object at the start of
- * every thread.
+ * A small interpreter for testing parallel code.
+ *
+ * The interpreter will read a list of lines consisting of "|"-separated statements, e.g. :
+ * sleep 2 ; unblock thread2 | wait thread2 time 2..5
+ * sendMessage "x" | obtainMessage = "x" time 0..1
+ *
+ * Each column runs in a different concurrent thread and all threads wait for each other in
+ * between lines. Each statement is split on ";" then matched with regular expressions in the
+ * instructionTable constant, which contains the code associated with each statement. The
+ * interpreter supports an object being passed to the interpretTestSpec() method to be passed
+ * in each lambda (think about the object under test), and an optional transform function to be
+ * executed on the object at the start of every thread.
*
* The time unit is defined in milliseconds by the interpretTimeUnit member, which has a default
* value but can be passed to the constructor. Whitespace is ignored.
@@ -34,13 +37,8 @@
* the regexp match, and the object. See the individual tests for the DSL of that test.
* Implementors for new interpreting languages are encouraged to look at the defaultInterpretTable
* constant below for an example of how to write an interpreting table.
- * Some expressions already exist by default and can be used by all interpreters. They include :
- * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
- * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
- * string "null" or an int. Returns Unit.
- * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
- * y time units.
- * EXPR // any text : comments are ignored.
+ * Some expressions already exist by default and can be used by all interpreters. Refer to
+ * getDefaultInstructions() below for a list and documentation.
*/
open class ConcurrentInterpreter<T>(
localInterpretTable: List<InterpretMatcher<T>>,
@@ -48,6 +46,9 @@
) {
private val interpretTable: List<InterpretMatcher<T>> =
localInterpretTable + getDefaultInstructions()
+ // The last time the thread became blocked, with base System.currentTimeMillis(). This should
+ // be set immediately before any time the thread gets blocked.
+ internal val lastBlockedTime = ThreadLocal<Long>()
// Split the line into multiple statements separated by ";" and execute them. Return whatever
// the last statement returned.
@@ -63,11 +64,28 @@
return code(this, r, match)
}
- // Spins as many threads as needed by the test spec and interpret each program concurrently,
- // having all threads waiting on a CyclicBarrier after each line.
- // |lineShift| says how many lines after the call the spec starts. This is used for error
- // reporting. Unfortunately AFAICT there is no way to get the line of an argument rather
- // than the line at which the expression starts.
+ /**
+ * Spins as many threads as needed by the test spec and interpret each program concurrently.
+ *
+ * All threads wait on a CyclicBarrier after each line.
+ * |lineShift| says how many lines after the call the spec starts. This is used for error
+ * reporting. Unfortunately AFAICT there is no way to get the line of an argument rather
+ * than the line at which the expression starts.
+ *
+ * This method is mostly meant for implementations that extend the ConcurrentInterpreter
+ * class to add their own directives and instructions. These may need to operate on some
+ * data, which can be passed in |initial|. For example, an interpreter specialized in callbacks
+ * may want to pass the callback there. In some cases, it's necessary that each thread
+ * performs a transformation *after* it starts on that value before starting ; in this case,
+ * the transformation can be passed to |threadTransform|. The default is to return |initial| as
+ * is. Look at some existing child classes of this interpreter for some examples of how this
+ * can be used.
+ *
+ * @param spec The test spec, as a string of lines separated by pipes.
+ * @param initial An initial value passed to all threads.
+ * @param lineShift How many lines after the call the spec starts, for error reporting.
+ * @param threadTransform an optional transformation that each thread will apply to |initial|
+ */
fun interpretTestSpec(
spec: String,
initial: T,
@@ -77,16 +95,25 @@
// For nice stack traces
val callSite = getCallingMethod()
val lines = spec.trim().trim('\n').split("\n").map { it.split("|") }
- // |threads| contains arrays of strings that make up the statements of a thread : in other
+ // |lines| contains arrays of strings that make up the statements of a thread : in other
// words, it's an array that contains a list of statements for each column in the spec.
+ // E.g. if the string is """
+ // a | b | c
+ // d | e | f
+ // """, then lines is [ [ "a", "b", "c" ], [ "d", "e", "f" ] ].
val threadCount = lines[0].size
assertTrue(lines.all { it.size == threadCount })
val threadInstructions = (0 until threadCount).map { i -> lines.map { it[i].trim() } }
+ // |threadInstructions| is a list where each element is the list of instructions for the
+ // thread at the index. In other words, it's just |lines| transposed. In the example
+ // above, it would be [ [ "a", "d" ], [ "b", "e" ], [ "c", "f" ] ]
+ // mapIndexed below will pass in |instructions| the list of instructions for this thread.
val barrier = CyclicBarrier(threadCount)
var crash: InterpretException? = null
threadInstructions.mapIndexed { threadIndex, instructions ->
Thread {
val threadLocal = threadTransform(initial)
+ lastBlockedTime.set(System.currentTimeMillis())
barrier.await()
var lineNum = 0
instructions.forEach {
@@ -104,6 +131,7 @@
callSite.lineNumber + lineNum + lineShift,
callSite.className, callSite.methodName, callSite.fileName, e)
}
+ lastBlockedTime.set(System.currentTimeMillis())
barrier.await()
}
}.also { it.start() }
@@ -138,6 +166,16 @@
}
}
+/**
+ * Default instructions available to all interpreters.
+ * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
+ * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
+ * string "null" or an int. Returns Unit.
+ * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
+ * y time units.
+ * EXPR // any text : comments are ignored.
+ * EXPR fails : checks that EXPR throws some exception.
+ */
private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>(
// Interpret an empty line as doing nothing.
Regex("") to { _, _, _ -> null },
@@ -145,8 +183,25 @@
Regex("(.*)//.*") to { i, t, r -> i.interpret(r.strArg(1), t) },
// Interpret "XXX time x..y" : run XXX and check it took at least x and not more than y
Regex("""(.*)\s*time\s*(\d+)\.\.(\d+)""") to { i, t, r ->
- val time = measureTimeMillis { i.interpret(r.strArg(1), t) }
- assertTrue(time in r.timeArg(2)..r.timeArg(3), "$time not in ${r.timeArg(2)..r.timeArg(3)}")
+ val lateStart = System.currentTimeMillis()
+ i.interpret(r.strArg(1), t)
+ val end = System.currentTimeMillis()
+ // There is uncertainty in measuring time.
+ // It takes some (small) time for the thread to even measure the time at which it
+ // starts interpreting the instruction. It is therefore possible that thread A sleeps for
+ // n milliseconds, and B expects to have waited for at least n milliseconds, but because
+ // B started measuring after 1ms or so, B thinks it didn't wait long enough.
+ // To avoid this, when the `time` instruction tests the instruction took at least X and
+ // at most Y, it tests X against a time measured since *before* the thread blocked but
+ // Y against a time measured as late as possible. This ensures that the timer is
+ // sufficiently lenient in both directions that there are no flaky measures.
+ val minTime = end - lateStart
+ val maxTime = end - i.lastBlockedTime.get()!!
+
+ assertTrue(maxTime >= r.timeArg(2),
+ "Should have taken at least ${r.timeArg(2)} but took less than $maxTime")
+ assertTrue(minTime <= r.timeArg(3),
+ "Should have taken at most ${r.timeArg(3)} but took more than $minTime")
},
// Interpret "XXX = YYY" : run XXX and assert its return value is equal to YYY. "null" supported
Regex("""(.*)\s*=\s*(null|\d+)""") to { i, t, r ->
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
index 3883511..733bd98 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
@@ -20,10 +20,10 @@
import androidx.annotation.NonNull;
-import com.android.net.module.util.BpfMap;
-import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.Struct;
+import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -32,20 +32,19 @@
/**
*
- * Fake BPF map class for tests that have no no privilege to access real BPF maps. All member
- * functions which eventually call JNI to access the real native BPF map are overridden.
+ * Fake BPF map class for tests that have no privilege to access real BPF maps. TestBpfMap does not
+ * load JNI and all member functions do not access real BPF maps.
*
- * Inherits from BpfMap instead of implementing IBpfMap so that any class using a BpfMap can use
- * this class in its tests.
+ * Implements IBpfMap so that any class using IBpfMap can use this class in its tests.
*
* @param <K> the key type
* @param <V> the value type
*/
-public class TestBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
+public class TestBpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
private final ConcurrentHashMap<K, V> mMap = new ConcurrentHashMap<>();
+ // TODO: Remove this constructor
public TestBpfMap(final Class<K> key, final Class<V> value) {
- super(key, value);
}
@Override
@@ -133,4 +132,8 @@
// TODO: consider using mocked #getFirstKey and #deleteEntry to implement.
mMap.clear();
}
+
+ @Override
+ public void close() throws IOException {
+ }
}