Tests for SharedMemory

Test: this
Change-Id: I414614c25b015d1bcbf1364f94615034488be84f
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
index ccc72a1..bf6f2fe 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -39,7 +39,8 @@
     src/android/os/cts/IParcelFileDescriptorPeer.aidl \
     src/android/os/cts/IEmptyService.aidl \
     src/android/os/cts/ISeccompIsolatedService.aidl \
-    src/android/os/cts/ISecondary.aidl
+    src/android/os/cts/ISecondary.aidl \
+    src/android/os/cts/ISharedMemoryService.aidl
 
 LOCAL_PACKAGE_NAME := CtsOsTestCases
 
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 846251f..a227cc3 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -76,6 +76,10 @@
             android:name="android.os.cts.CrossProcessExceptionService"
             android:process=":green"
             android:exported="true" />
+        <service
+            android:name="android.os.cts.SharedMemoryService"
+            android:process=":sharedmem"
+            android:exported="false" />
 
         <service android:name="android.os.cts.LocalService">
             <intent-filter>
diff --git a/tests/tests/os/src/android/os/cts/ISharedMemoryService.aidl b/tests/tests/os/src/android/os/cts/ISharedMemoryService.aidl
new file mode 100644
index 0000000..651c62a
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/ISharedMemoryService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.os.cts;
+
+import android.os.SharedMemory;
+
+interface ISharedMemoryService {
+    void setup(in SharedMemory memory, int prot);
+    byte read(int index);
+    void write(int index, byte value);
+}
diff --git a/tests/tests/os/src/android/os/cts/MemoryFileTest.java b/tests/tests/os/src/android/os/cts/MemoryFileTest.java
index def73b2..6e6eba8 100644
--- a/tests/tests/os/src/android/os/cts/MemoryFileTest.java
+++ b/tests/tests/os/src/android/os/cts/MemoryFileTest.java
@@ -56,7 +56,7 @@
             byte[] data = new byte[512];
             mMemoryFile.writeBytes(data, srcOffset, destOffset, count);
             fail("MemoryFile should throw IndexOutOfBoundsException here.");
-        } catch (IndexOutOfBoundsException e) {
+        } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
             // expected
         }
     }
@@ -101,13 +101,10 @@
         mMemoryFile = new MemoryFile("Test File", 512);
         assertEquals(512, mMemoryFile.length());
 
-        mMemoryFile = new MemoryFile("Test File", 0);
-        assertEquals(0, mMemoryFile.length());
-
         try {
             mMemoryFile = new MemoryFile("Test File", -512);
             fail();
-        } catch (IOException expected) {
+        } catch (IOException | IllegalArgumentException expected) {
         }
     }
 
@@ -142,7 +139,7 @@
             byte[] data = new byte[512];
             mMemoryFile.readBytes(data, srcOffset, destOffset, count);
             fail("MemoryFile should throw IndexOutOfBoundsException here.");
-        } catch (IndexOutOfBoundsException e) {
+        } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
             // expected
         }
     }
diff --git a/tests/tests/os/src/android/os/cts/SharedMemoryService.java b/tests/tests/os/src/android/os/cts/SharedMemoryService.java
new file mode 100644
index 0000000..fcc38d5
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SharedMemoryService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.os.cts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+
+import java.nio.ByteBuffer;
+
+public class SharedMemoryService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new SharedMemoryServiceImpl();
+    }
+
+    private static class SharedMemoryServiceImpl extends ISharedMemoryService.Stub {
+        private SharedMemory mSharedMemory;
+        private ByteBuffer mMappedBuffer;
+
+        @Override
+        public void setup(SharedMemory memory, int prot) throws RemoteException {
+            mSharedMemory = memory;
+            try {
+                mMappedBuffer = mSharedMemory.map(prot, 0, mSharedMemory.getSize());
+            } catch (ErrnoException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public byte read(int index) throws RemoteException {
+            // Although we expect only one client we need to insert memory barriers to ensure
+            // visibility
+            synchronized (mMappedBuffer) {
+                return mMappedBuffer.get(index);
+            }
+        }
+
+        @Override
+        public void write(int index, byte value) throws RemoteException {
+            // Although we expect only one client we need to insert memory barriers to ensure
+            // visibility
+            synchronized (mMappedBuffer) {
+                mMappedBuffer.put(index, value);
+            }
+        }
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/SharedMemoryTest.java b/tests/tests/os/src/android/os/cts/SharedMemoryTest.java
new file mode 100644
index 0000000..c40cdc8
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SharedMemoryTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2017 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.os.cts;
+
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.system.OsConstants.PROT_READ;
+import static android.system.OsConstants.PROT_WRITE;
+
+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.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+
+import com.google.common.util.concurrent.AbstractFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SharedMemoryTest {
+
+    private Instrumentation mInstrumentation;
+    private Intent mRemoteIntent;
+    private PeerConnection mRemoteConnection;
+    private ISharedMemoryService mRemote;
+
+    public static class PeerConnection extends AbstractFuture<ISharedMemoryService>
+            implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            set(ISharedMemoryService.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+
+        @Override
+        public ISharedMemoryService get() throws InterruptedException, ExecutionException {
+            try {
+                return get(5, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = mInstrumentation.getContext();
+        // Bring up both remote processes and wire them to each other
+        mRemoteIntent = new Intent();
+        mRemoteIntent.setComponent(new ComponentName(
+                "android.os.cts", "android.os.cts.SharedMemoryService"));
+        mRemoteConnection = new PeerConnection();
+        getContext().bindService(mRemoteIntent, mRemoteConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+        mRemote = mRemoteConnection.get();
+    }
+
+    @After
+    public void tearDown() {
+        final Context context = mInstrumentation.getContext();
+        context.unbindService(mRemoteConnection);
+    }
+
+    @Test
+    public void testReadWrite() throws RemoteException, ErrnoException {
+        try (SharedMemory sharedMemory = SharedMemory.create(null, 1)) {
+            ByteBuffer buffer = sharedMemory.mapReadWrite();
+            mRemote.setup(sharedMemory, PROT_READ | PROT_WRITE);
+
+            byte expected = 5;
+            buffer.put(0, expected);
+            assertEquals(expected, buffer.get(0));
+            // Memory barrier
+            synchronized (sharedMemory) {}
+            assertEquals(expected, mRemote.read(0));
+            expected = 10;
+            mRemote.write(0, expected);
+            // Memory barrier
+            synchronized (sharedMemory) {}
+            assertEquals(expected, buffer.get(0));
+            SharedMemory.unmap(buffer);
+        }
+    }
+
+    @Test
+    public void testReadOnly() throws RemoteException, ErrnoException {
+        try (SharedMemory sharedMemory = SharedMemory.create(null, 1)) {
+            ByteBuffer buffer = sharedMemory.mapReadWrite();
+            sharedMemory.setProtect(PROT_READ);
+            mRemote.setup(sharedMemory, PROT_READ);
+
+            byte expected = 15;
+            buffer.put(0, expected);
+            assertEquals(expected, buffer.get(0));
+            // Memory barrier
+            synchronized (sharedMemory) {}
+            assertEquals(expected, mRemote.read(0));
+            expected = 20;
+            try {
+                mRemote.write(0, expected);
+                fail("write shouldn't have worked, should be read only");
+            } catch (Exception e) {}
+
+            buffer.put(0, expected);
+            assertEquals(expected, buffer.get(0));
+            // Memory barrier
+            synchronized (sharedMemory) {}
+            assertEquals(expected, mRemote.read(0));
+        }
+    }
+
+    @Test
+    public void testUseAfterClose() throws RemoteException, ErrnoException {
+        ByteBuffer buffer;
+        try (SharedMemory sharedMemory = SharedMemory.create(null, 1)) {
+            buffer = sharedMemory.mapReadWrite();
+            mRemote.setup(sharedMemory, PROT_READ | PROT_WRITE);
+        }
+        byte expected = 5;
+        buffer.put(0, expected);
+        assertEquals(expected, buffer.get(0));
+        // Memory barrier
+        synchronized (buffer) {}
+        assertEquals(expected, mRemote.read(0));
+        expected = 10;
+        mRemote.write(0, expected);
+        // Memory barrier
+        synchronized (buffer) {}
+        assertEquals(expected, buffer.get(0));
+        SharedMemory.unmap(buffer);
+    }
+
+    @Test
+    public void testGetFd() throws ErrnoException {
+        SharedMemory sharedMemory = SharedMemory.create("hello", 1024);
+        assertNotEquals(-1, sharedMemory.getFd());
+        assertNotNull(sharedMemory.getFileDescriptor());
+        assertTrue(sharedMemory.getFileDescriptor().valid());
+        sharedMemory.close();
+        assertEquals(-1, sharedMemory.getFd());
+        assertNotNull(sharedMemory.getFileDescriptor());
+        assertTrue(!sharedMemory.getFileDescriptor().valid());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testInvalidCreate() throws ErrnoException {
+        SharedMemory.create(null, -1);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testInvalidMapProt() throws ErrnoException {
+        try (SharedMemory sharedMemory = SharedMemory.create(null, 1)) {
+            sharedMemory.map(-1, 0, 1);
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testInvalidSetProt() throws ErrnoException {
+        try (SharedMemory sharedMemory = SharedMemory.create(null, 1)) {
+            sharedMemory.setProtect(-1);
+        }
+    }
+
+    @Test
+    public void testSetProtAddProt() throws ErrnoException {
+        try (SharedMemory sharedMemory = SharedMemory.create(null, 1)) {
+            assertTrue(sharedMemory.setProtect(OsConstants.PROT_READ));
+            assertTrue(sharedMemory.setProtect(OsConstants.PROT_READ));
+            assertFalse(sharedMemory.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE));
+            assertTrue(sharedMemory.setProtect(OsConstants.PROT_NONE));
+            assertFalse(sharedMemory.setProtect(OsConstants.PROT_READ));
+        }
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testMapAfterClose() throws ErrnoException {
+        SharedMemory sharedMemory = SharedMemory.create(null, 1);
+        sharedMemory.close();
+        sharedMemory.mapReadWrite();
+    }
+
+    @Test(expected=ReadOnlyBufferException.class)
+    public void testWriteToReadOnly() throws ErrnoException {
+        try (SharedMemory sharedMemory = SharedMemory.create(null, 1)) {
+            sharedMemory.setProtect(PROT_READ);
+            ByteBuffer buffer = null;
+            try {
+                buffer = sharedMemory.mapReadWrite();
+                fail("Should have thrown an exception");
+            } catch (ErrnoException ex) {
+                assertEquals(OsConstants.EPERM, ex.errno);
+            }
+            buffer = sharedMemory.mapReadOnly();
+            assertTrue(buffer.isReadOnly());
+            buffer.put(0, (byte) 0);
+        }
+    }
+}