Migrate file system tests to CTSv2

bug:21762834
Change-Id: I361864f427fe37799b8e577157f50613af010c20
diff --git a/tests/filesystem/Android.mk b/tests/filesystem/Android.mk
new file mode 100644
index 0000000..00e5ee7
--- /dev/null
+++ b/tests/filesystem/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2012 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil compatibility-device-util ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsFileSystemTestCases
+
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
+LOCAL_SDK_VERSION := 16
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/filesystem/AndroidManifest.xml b/tests/filesystem/AndroidManifest.xml
new file mode 100644
index 0000000..c02ac77
--- /dev/null
+++ b/tests/filesystem/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.filesystem.cts">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="android.filesystem.cts"
+            android:label="CTS tests for file system" />
+</manifest>
diff --git a/tests/filesystem/AndroidTest.xml b/tests/filesystem/AndroidTest.xml
new file mode 100644
index 0000000..65d4296
--- /dev/null
+++ b/tests/filesystem/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<configuration description="Config for CTS File System Test Cases">
+    <include name="common-config" />
+    <option name="apk-installer:test-file-name" value="CtsFileSystemTestCases.apk" />
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="android.filesystem.cts" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/filesystem/src/android/filesystem/cts/AlmostFullTest.java b/tests/filesystem/src/android/filesystem/cts/AlmostFullTest.java
new file mode 100644
index 0000000..991f9bf
--- /dev/null
+++ b/tests/filesystem/src/android/filesystem/cts/AlmostFullTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012 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 android.filesystem.cts;
+
+import android.util.Log;
+
+import android.cts.util.CtsAndroidTestCase;
+import android.cts.util.SystemUtil;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.cts.util.TimeoutReq;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AlmostFullTest extends CtsAndroidTestCase {
+
+    private static final String DIR_INITIAL_FILL = "INITIAL_FILL";
+    private static final String DIR_SEQ_UPDATE = "SEQ_UPDATE";
+    private static final String DIR_RANDOM_WR = "RANDOM_WR";
+    private static final String DIR_RANDOM_RD = "RANDOM_RD";
+    private static final String TAG = "AlmostFullTest";
+
+    private static final long FREE_SPACE_FINAL = 1000L * 1024 * 1024L;
+
+    // test runner creates multiple instances at the begging.
+    // use that to fill disk only once.
+    // set as final to initialize it only once
+    private static final AtomicInteger mRefCounter = new AtomicInteger(0);
+    private static final AtomicBoolean mDiskFilled = new AtomicBoolean(false);
+
+    public AlmostFullTest() {
+        int currentCounter = mRefCounter.incrementAndGet();
+        Log.i(TAG, "++currentCounter: " + currentCounter);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (mDiskFilled.compareAndSet(false, true)) {
+            Log.i(TAG, "Filling disk");
+            // initial fill done in two stage as disk can be filled by other
+            // components
+            long freeDisk = SystemUtil.getFreeDiskSize(getContext());
+            long diskToFill = freeDisk - FREE_SPACE_FINAL;
+            if (diskToFill >= 0) {
+                Log.i(TAG, "free disk " + freeDisk + ", to fill " + diskToFill);
+            } else {
+                Log.i(TAG, "free disk " + freeDisk + " too small, needs " + FREE_SPACE_FINAL);
+                return;
+            }
+            final long MAX_FILE_SIZE_TO_FILL = 1024L * 1024L * 1024L;
+            long filled = 0;
+            while (filled < diskToFill) {
+                long toFill = diskToFill - filled;
+                if (toFill > MAX_FILE_SIZE_TO_FILL) {
+                    toFill = MAX_FILE_SIZE_TO_FILL;
+                }
+                Log.i(TAG, "Generating file " + toFill);
+                FileUtil.createNewFilledFile(getContext(),
+                        DIR_INITIAL_FILL, toFill);
+                filled += toFill;
+            }
+        }
+        Log.i(TAG, "free disk " + SystemUtil.getFreeDiskSize(getContext()));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        Log.i(TAG, "tearDown free disk " + SystemUtil.getFreeDiskSize(getContext()));
+        int currentCounter = mRefCounter.decrementAndGet();
+        Log.i(TAG, "--currentCounter: " + currentCounter);
+        if (currentCounter == 0) {
+            FileUtil.removeFileOrDir(getContext(), DIR_INITIAL_FILL);
+        }
+        FileUtil.removeFileOrDir(getContext(), DIR_SEQ_UPDATE);
+        FileUtil.removeFileOrDir(getContext(), DIR_RANDOM_WR);
+        FileUtil.removeFileOrDir(getContext(), DIR_RANDOM_RD);
+        Log.i(TAG, "tearDown free disk " + SystemUtil.getFreeDiskSize(getContext()));
+        super.tearDown();
+    }
+
+    @TimeoutReq(minutes = 30)
+    public void testSequentialUpdate() throws Exception {
+        // now about freeSpaceToLeave should be left
+        // and try updating exceeding the free space size
+        final long FILE_SIZE = 400L * 1024L * 1024L;
+        long freeDisk = SystemUtil.getFreeDiskSize(getContext());
+        Log.i(TAG, "Now free space is " + freeDisk);
+        if (freeDisk < FILE_SIZE) {
+            Log.w(TAG, "too little space: " + freeDisk);
+            return;
+        }
+        final int BUFFER_SIZE = 10 * 1024 * 1024;
+        final int NUMBER_REPETITION = 10;
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, report, FILE_SIZE,
+                BUFFER_SIZE, NUMBER_REPETITION);
+        report.submit(getInstrumentation());
+    }
+
+    // TODO: file size too small and caching will give wrong better result.
+    // needs to flush cache by reading big files per each read.
+    @TimeoutReq(minutes = 60)
+    public void testRandomRead() throws Exception {
+        final int BUFFER_SIZE = 4 * 1024;
+        final long fileSize = 400L * 1024L * 1024L;
+        long freeDisk = SystemUtil.getFreeDiskSize(getContext());
+        if (freeDisk < fileSize) {
+            Log.w(TAG, "too little space: " + freeDisk);
+            return;
+        }
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, report, fileSize, BUFFER_SIZE);
+        report.submit(getInstrumentation());
+    }
+
+    @TimeoutReq(minutes = 60)
+    public void testRandomUpdate() throws Exception {
+        final int BUFFER_SIZE = 4 * 1024;
+        final long fileSize = 256L * 1024L * 1024L;
+        long freeDisk = SystemUtil.getFreeDiskSize(getContext());
+        if (freeDisk < fileSize) {
+            Log.w(TAG, "too little space: " + freeDisk);
+            return;
+        }
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, report, fileSize, BUFFER_SIZE);
+        report.submit(getInstrumentation());
+    }
+}
diff --git a/tests/filesystem/src/android/filesystem/cts/FileUtil.java b/tests/filesystem/src/android/filesystem/cts/FileUtil.java
new file mode 100755
index 0000000..baefaa0
--- /dev/null
+++ b/tests/filesystem/src/android/filesystem/cts/FileUtil.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2012 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 android.filesystem.cts;
+
+import android.content.Context;
+import android.cts.util.SystemUtil;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.RandomAccessFile;
+import java.util.Random;
+
+public class FileUtil {
+    private static final String TAG = "FileUtil";
+    private static final Random mRandom = new Random(0);
+    private static long mFileId = 0;
+    /**
+     * create array with different data per each call
+     *
+     * @param length
+     * @param randomSeed
+     * @return
+     */
+    public static byte[] generateRandomData(int length) {
+        byte[] buffer = new byte[length];
+        int val = mRandom.nextInt();
+        for (int i = 0; i < length / 4; i++) {
+            // in little-endian
+            buffer[i * 4] = (byte)(val & 0x000000ff);
+            buffer[i * 4 + 1] = (byte)((val & 0x0000ff00) >> 8);
+            buffer[i * 4 + 2] = (byte)((val & 0x00ff0000) >> 16);
+            buffer[i * 4 + 3] = (byte)((val & 0xff000000) >> 24);
+            val++;
+        }
+        for (int i = (length / 4) * 4; i < length; i++) {
+            buffer[i] = 0;
+        }
+        return buffer;
+    }
+
+    /**
+     * create a new file under the given dirName.
+     * Existing files will not be affected.
+     * @param context
+     * @param dirName
+     * @return
+     */
+    public static File createNewFile(Context context, String dirName) {
+        File topDir = new File(context.getFilesDir(), dirName);
+        topDir.mkdir();
+        String[] list = topDir.list();
+
+        String newFileName;
+        while (true) {
+            newFileName = Long.toString(mFileId);
+            boolean fileExist = false;
+            for (String child : list) {
+                if (child.equals(newFileName)) {
+                    fileExist = true;
+                    break;
+                }
+            }
+            if (!fileExist) {
+                break;
+            }
+            mFileId++;
+        }
+        mFileId++;
+        //Log.i(TAG, "filename" + Long.toString(mFileId));
+        return new File(topDir, newFileName);
+    }
+
+    /**
+     * create multiple new files
+     * @param context
+     * @param dirName
+     * @param count number of files to create
+     * @return
+     */
+    public static File[] createNewFiles(Context context, String dirName, int count) {
+        File[] files = new File[count];
+        for (int i = 0; i < count; i++) {
+            files[i] = createNewFile(context, dirName);
+        }
+        return files;
+    }
+
+    /**
+     * write file with given byte array
+     * @param file
+     * @param data
+     * @param append will append if set true. Otherwise, write from beginning
+     * @throws IOException
+     */
+    public static void writeFile(File file, byte[] data, boolean append) throws IOException {
+        final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC
+        if (append) {
+            randomFile.seek(randomFile.length());
+        } else {
+            randomFile.seek(0L);
+        }
+        randomFile.write(data);
+        randomFile.close();
+    }
+
+    /**
+     * create a new file with given length.
+     * @param context
+     * @param dirName
+     * @param length
+     * @return
+     * @throws IOException
+     */
+    public static File createNewFilledFile(Context context, String dirName, long length)
+            throws IOException {
+        final int BUFFER_SIZE = 10 * 1024 * 1024;
+        File file = createNewFile(context, dirName);
+        FileOutputStream out = new FileOutputStream(file);
+        byte[] data = generateRandomData(BUFFER_SIZE);
+        long written = 0;
+        while (written < length) {
+            out.write(data);
+            written += BUFFER_SIZE;
+        }
+        out.flush();
+        out.close();
+        return file;
+    }
+
+    /**
+     * remove given file or directory under the current app's files dir.
+     * @param context
+     * @param name
+     */
+    public static void removeFileOrDir(Context context, String name) {
+        File entry = new File(context.getFilesDir(), name);
+        if (entry.exists()) {
+            removeEntry(entry);
+        }
+    }
+
+    private static void removeEntry(File entry) {
+        if (entry.isDirectory()) {
+            String[] children = entry.list();
+            for (String child : children) {
+                removeEntry(new File(entry, child));
+            }
+        }
+        Log.i(TAG, "delete file " + entry.getAbsolutePath());
+        entry.delete();
+    }
+
+    /**
+     * measure time taken for each IO run with amount R/W
+     * @param count
+     * @param run
+     * @param readAmount returns amount of read in bytes for each interval.
+     *        Value will not be written if /proc/self/io does not exist.
+     * @param writeAmount returns amount of write in bytes for each interval.
+     * @return time per each interval
+     * @throws IOException
+     */
+    public static double[] measureIO(int count, double[] readAmount, double[] writeAmount,
+            MeasureRun run)  throws Exception {
+        double[] result = new double[count];
+        File procIo = new File("/proc/self/io");
+        boolean measureIo = procIo.exists() && procIo.canRead();
+        long prev = System.currentTimeMillis();
+        RWAmount prevAmount = new RWAmount();
+        if (measureIo) {
+            prevAmount = getRWAmount(procIo);
+        }
+        for (int i = 0; i < count; i++) {
+            run.run(i);
+            long current =  System.currentTimeMillis();
+            result[i] = current - prev;
+            prev = current;
+            if (measureIo) {
+                RWAmount currentAmount = getRWAmount(procIo);
+                readAmount[i] = currentAmount.mRd - prevAmount.mRd;
+                writeAmount[i] = currentAmount.mWr - prevAmount.mWr;
+                prevAmount = currentAmount;
+            }
+        }
+        return result;
+    }
+
+    private static class RWAmount {
+        public double mRd = 0.0;
+        public double mWr = 0.0;
+    };
+
+    private static RWAmount getRWAmount(File file) throws IOException {
+        RWAmount amount = new RWAmount();
+
+        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+        String line;
+        while((line = br.readLine())!= null) {
+            if (line.startsWith("read_bytes")) {
+                amount.mRd = Double.parseDouble(line.split(" ")[1]);
+            } else if (line.startsWith("write_bytes")) {
+                amount.mWr = Double.parseDouble(line.split(" ")[1]);
+            }
+        }
+        br.close();
+        return amount;
+    }
+
+    /**
+     * get file size exceeding total memory size ( 2x total memory).
+     * The size is rounded in bufferSize. And the size will be bigger than 400MB.
+     * @param context
+     * @param bufferSize
+     * @return file size or 0 if there is not enough space.
+     */
+    public static long getFileSizeExceedingMemory(Context context, int bufferSize) {
+        long freeDisk = SystemUtil.getFreeDiskSize(context);
+        long memSize = SystemUtil.getTotalMemory(context);
+        long diskSizeTarget = (2 * memSize / bufferSize) * bufferSize;
+        final long minimumDiskSize = (512L * 1024L * 1024L / bufferSize) * bufferSize;
+        final long reservedDiskSize = (50L * 1024L * 1024L / bufferSize) * bufferSize;
+        if ( diskSizeTarget < minimumDiskSize ) {
+            diskSizeTarget = minimumDiskSize;
+        }
+        if (diskSizeTarget > freeDisk) {
+            Log.i(TAG, "Free disk size " + freeDisk + " too small");
+            return 0;
+        }
+        if ((freeDisk - diskSizeTarget) < reservedDiskSize) {
+            diskSizeTarget -= reservedDiskSize;
+        }
+        return diskSizeTarget;
+    }
+
+    /**
+     *
+     * @param context
+     * @param dirName
+     * @param report
+     * @param fileSize
+     * @param bufferSize should be power of two
+     * @throws IOException
+     */
+    public static void doRandomReadTest(Context context, String dirName, ReportLog report,
+            long fileSize, int bufferSize) throws Exception {
+        File file = FileUtil.createNewFilledFile(context,
+                dirName, fileSize);
+
+        final byte[] data = FileUtil.generateRandomData(bufferSize);
+        Random random = new Random(0);
+        final int totalReadCount = (int)(fileSize / bufferSize);
+        final int[] readOffsets = new int[totalReadCount];
+        for (int i = 0; i < totalReadCount; i++) {
+            // align in buffer size
+            readOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) &
+                    ~(bufferSize - 1);
+        }
+        final int runsInOneGo = 16;
+        final int readsInOneMeasure = totalReadCount / runsInOneGo;
+
+        final RandomAccessFile randomFile = new RandomAccessFile(file, "rw"); // do not need O_SYNC
+        double[] rdAmount = new double[runsInOneGo];
+        double[] wrAmount = new double[runsInOneGo];
+        double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() {
+
+            @Override
+            public void run(int i) throws IOException {
+                Log.i(TAG, "starting " + i + " -th round");
+                int start = i * readsInOneMeasure;
+                int end = (i + 1) * readsInOneMeasure;
+                for (int j = start; j < end; j++) {
+                    randomFile.seek(readOffsets[j]);
+                    randomFile.read(data);
+                }
+            }
+        });
+        randomFile.close();
+        double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
+                times);
+        report.addValues("read throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        // This is just the amount of IO returned from kernel. So this is performance neutral.
+        report.addValues("read amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
+        Stat.StatResult stat = Stat.getStat(mbps);
+
+        report.setSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
+    }
+
+    /**
+     *
+     * @param context
+     * @param dirName
+     * @param report
+     * @param fileSize
+     * @param bufferSize should be power of two
+     * @throws IOException
+     */
+    public static void doRandomWriteTest(Context context, String dirName, ReportLog report,
+            long fileSize, int bufferSize) throws Exception {
+        File file = FileUtil.createNewFilledFile(context,
+                dirName, fileSize);
+        final byte[] data = FileUtil.generateRandomData(bufferSize);
+        Random random = new Random(0);
+        final int totalWriteCount = (int)(fileSize / bufferSize);
+        final int[] writeOffsets = new int[totalWriteCount];
+        for (int i = 0; i < totalWriteCount; i++) {
+            writeOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) &
+                    ~(bufferSize - 1);
+        }
+        final int runsInOneGo = 16;
+        final int writesInOneMeasure = totalWriteCount / runsInOneGo;
+
+        final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC
+        double[] rdAmount = new double[runsInOneGo];
+        double[] wrAmount = new double[runsInOneGo];
+        double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() {
+
+            @Override
+            public void run(int i) throws IOException {
+                Log.i(TAG, "starting " + i + " -th round");
+                int start = i * writesInOneMeasure;
+                int end = (i + 1) * writesInOneMeasure;
+                for (int j = start; j < end; j++) {
+                    randomFile.seek(writeOffsets[j]);
+                    randomFile.write(data);
+                }
+            }
+        });
+        randomFile.close();
+        double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
+                times);
+        report.addValues("write throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.addValues("write amount", wrAmount, ResultType.NEUTRAL,
+                ResultUnit.BYTE);
+        Stat.StatResult stat = Stat.getStat(mbps);
+
+        report.setSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
+    }
+
+    /**
+     *
+     * @param context
+     * @param dirName
+     * @param report
+     * @param fileSize fileSize should be multiple of bufferSize.
+     * @param bufferSize
+     * @param numberRepetition
+     * @throws IOException
+     */
+    public static void doSequentialUpdateTest(Context context, String dirName, ReportLog report,
+            long fileSize, int bufferSize, int numberRepetition) throws Exception {
+        File file = FileUtil.createNewFilledFile(context,
+                dirName, fileSize);
+        final byte[] data = FileUtil.generateRandomData(bufferSize);
+        int numberRepeatInOneRun = (int)(fileSize / bufferSize);
+        double[] mbpsAll = new double[numberRepetition * numberRepeatInOneRun];
+        for (int i = 0; i < numberRepetition; i++) {
+            Log.i(TAG, "starting " + i + " -th round");
+            final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd");  // force O_SYNC
+            randomFile.seek(0L);
+            double[] times = MeasureTime.measure(numberRepeatInOneRun, new MeasureRun() {
+
+                @Override
+                public void run(int i) throws IOException {
+                    randomFile.write(data);
+                }
+            });
+            randomFile.close();
+            double[] mbps = Stat.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
+                    times);
+            report.addValues(i + "-th round throughput",
+                    mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+            int offset = i * numberRepeatInOneRun;
+            for (int j = 0; j < mbps.length; j++) {
+                mbpsAll[offset + j] = mbps[j];
+            }
+        }
+        Stat.StatResult stat = Stat.getStat(mbpsAll);
+        report.setSummary("update throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
+    }
+}
diff --git a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
new file mode 100644
index 0000000..dff3723
--- /dev/null
+++ b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 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 android.filesystem.cts;
+
+import android.cts.util.CtsAndroidTestCase;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.cts.util.TimeoutReq;
+
+public class RandomRWTest extends CtsAndroidTestCase {
+    private static final String DIR_RANDOM_WR = "RANDOM_WR";
+    private static final String DIR_RANDOM_RD = "RANDOM_RD";
+
+    @Override
+    protected void tearDown() throws Exception {
+        FileUtil.removeFileOrDir(getContext(), DIR_RANDOM_WR);
+        FileUtil.removeFileOrDir(getContext(), DIR_RANDOM_RD);
+        super.tearDown();
+    }
+
+    @TimeoutReq(minutes = 60)
+    public void testRandomRead() throws Exception {
+        final int READ_BUFFER_SIZE = 4 * 1024;
+        final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), READ_BUFFER_SIZE);
+        if (fileSize == 0) { // not enough space, give up
+            return;
+        }
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, report, fileSize,
+                READ_BUFFER_SIZE);
+        report.submit(getInstrumentation());
+    }
+
+    // It is taking too long in some device, and thus cannot run multiple times
+    @TimeoutReq(minutes = 60)
+    public void testRandomUpdate() throws Exception {
+        final int WRITE_BUFFER_SIZE = 4 * 1024;
+        final long fileSize = 256 * 1024 * 1024;
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, report, fileSize,
+                WRITE_BUFFER_SIZE);
+        report.submit(getInstrumentation());
+    }
+}
diff --git a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
new file mode 100644
index 0000000..3b7a45f
--- /dev/null
+++ b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 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 android.filesystem.cts;
+
+import android.cts.util.CtsAndroidTestCase;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MeasureRun;
+import com.android.compatibility.common.util.MeasureTime;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
+import com.android.cts.util.TimeoutReq;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public class SequentialRWTest extends CtsAndroidTestCase {
+    private static final String DIR_SEQ_WR = "SEQ_WR";
+    private static final String DIR_SEQ_UPDATE = "SEQ_UPDATE";
+    private static final String DIR_SEQ_RD = "SEQ_RD";
+    private static final int BUFFER_SIZE = 10 * 1024 * 1024;
+
+    @Override
+    protected void tearDown() throws Exception {
+        FileUtil.removeFileOrDir(getContext(), DIR_SEQ_WR);
+        FileUtil.removeFileOrDir(getContext(), DIR_SEQ_UPDATE);
+        FileUtil.removeFileOrDir(getContext(), DIR_SEQ_RD);
+        super.tearDown();
+    }
+
+    @TimeoutReq(minutes = 30)
+    public void testSingleSequentialWrite() throws Exception {
+        final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
+        if (fileSize == 0) { // not enough space, give up
+            return;
+        }
+        final int numberOfFiles =(int)(fileSize / BUFFER_SIZE);
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValue("files", numberOfFiles, ResultType.NEUTRAL, ResultUnit.COUNT);
+        final byte[] data = FileUtil.generateRandomData(BUFFER_SIZE);
+        final File[] files = FileUtil.createNewFiles(getContext(), DIR_SEQ_WR,
+                numberOfFiles);
+        double[] rdAmount = new double[numberOfFiles];
+        double[] wrAmount = new double[numberOfFiles];
+        double[] times = FileUtil.measureIO(numberOfFiles, rdAmount, wrAmount, new MeasureRun() {
+
+            @Override
+            public void run(int i) throws IOException {
+                FileUtil.writeFile(files[i], data, false);
+            }
+        });
+        double[] mbps = Stat.calcRatePerSecArray((double)BUFFER_SIZE / 1024 / 1024, times);
+        report.addValues("write throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.addValues("write amount", wrAmount, ResultType.NEUTRAL,
+                ResultUnit.BYTE);
+        Stat.StatResult stat = Stat.getStat(mbps);
+        report.setSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
+        report.submit(getInstrumentation());
+    }
+
+    @TimeoutReq(minutes = 60)
+    public void testSingleSequentialUpdate() throws Exception {
+        final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
+        if (fileSize == 0) { // not enough space, give up
+            return;
+        }
+        final int NUMBER_REPETITION = 6;
+        DeviceReportLog report = new DeviceReportLog();
+        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, report, fileSize,
+                BUFFER_SIZE, NUMBER_REPETITION);
+        report.submit(getInstrumentation());
+    }
+
+    @TimeoutReq(minutes = 30)
+    public void testSingleSequentialRead() throws Exception {
+        final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
+        if (fileSize == 0) { // not enough space, give up
+            return;
+        }
+        long start = System.currentTimeMillis();
+        final File file = FileUtil.createNewFilledFile(getContext(),
+                DIR_SEQ_RD, fileSize);
+        long finish = System.currentTimeMillis();
+        DeviceReportLog report = new DeviceReportLog();
+        report.addValue("write throughput for test file of length " + fileSize,
+                Stat.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start),
+                ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+
+        final int NUMBER_READ = 10;
+
+        final byte[] data = new byte[BUFFER_SIZE];
+        double[] times = MeasureTime.measure(NUMBER_READ, new MeasureRun() {
+
+            @Override
+            public void run(int i) throws IOException {
+                final FileInputStream in = new FileInputStream(file);
+                long read = 0;
+                while (read < fileSize) {
+                    in.read(data);
+                    read += BUFFER_SIZE;
+                }
+                in.close();
+            }
+        });
+        double[] mbps = Stat.calcRatePerSecArray((double)fileSize / 1024 / 1024, times);
+        report.addValues("read throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        Stat.StatResult stat = Stat.getStat(mbps);
+        report.setSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
+        report.submit(getInstrumentation());
+    }
+}