diff --git a/tests/MidiTests/Android.bp b/tests/MidiTests/Android.bp new file mode 100644 index 0000000000000..254770d21818d --- /dev/null +++ b/tests/MidiTests/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "MidiTests", + srcs: ["**/*.java"], + static_libs: [ + "androidx.test.rules", + "mockito-target-inline-minus-junit4", + "platform-test-annotations", + "services.midi", + "truth-prebuilt", + ], + jni_libs: ["libdexmakerjvmtiagent"], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], +} diff --git a/tests/MidiTests/AndroidManifest.xml b/tests/MidiTests/AndroidManifest.xml new file mode 100644 index 0000000000000..0ee1b44937648 --- /dev/null +++ b/tests/MidiTests/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/tests/MidiTests/AndroidTest.xml b/tests/MidiTests/AndroidTest.xml new file mode 100644 index 0000000000000..9320f0aac090d --- /dev/null +++ b/tests/MidiTests/AndroidTest.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/tests/MidiTests/OWNERS b/tests/MidiTests/OWNERS new file mode 100644 index 0000000000000..af273a6f50e0c --- /dev/null +++ b/tests/MidiTests/OWNERS @@ -0,0 +1 @@ +include /services/midi/OWNERS diff --git a/tests/MidiTests/TEST_MAPPING b/tests/MidiTests/TEST_MAPPING new file mode 100644 index 0000000000000..60416a8ab3f93 --- /dev/null +++ b/tests/MidiTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "MidiTests" + } + ] +} diff --git a/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java new file mode 100644 index 0000000000000..1659cc07f0214 --- /dev/null +++ b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2023 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.server.midi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.midi.MidiEventMultiScheduler; +import com.android.internal.midi.MidiEventScheduler; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Random; + +/** + * Unit tests for com.android.internal.midi.MidiEventMultiScheduler. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class MidiEventMultiSchedulerTest { + private byte[] generateRandomByteStream(Random rnd, int size) { + byte[] output = new byte[size]; + rnd.nextBytes(output); + return output; + } + + private void compareByteArrays(byte[] expectedArray, byte[] outputArray) { + assertEquals(expectedArray.length, outputArray.length); + for (int i = 0; i < outputArray.length; i++) { + assertEquals(expectedArray[i], outputArray[i]); + } + } + + private long timeFromNow(long milliseconds) { + return System.nanoTime() + 1000000L * milliseconds; + } + + @Test + public void testMultiScheduler() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0, (byte) 0xf7}, + 0, 2, timeFromNow(100))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1, (byte) 0xf2}, + 0, 2, timeFromNow(200))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf3, (byte) 0xf4}, + 0, 2, timeFromNow(300))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6}, + 0, 2, timeFromNow(400))); + assertTrue(multiScheduler.waitNextEvent()); + assertNotNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNotNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNotNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNotNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerLargeData() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + + Random rnd = new Random(42); + + final int arraySize = 1000; + byte[] expectedArray = generateRandomByteStream(rnd, arraySize); + + scheduler0.add(scheduler0.createScheduledEvent(expectedArray, 0, arraySize, + timeFromNow(100))); + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + compareByteArrays(expectedArray, event.data); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + scheduler0.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerMultiClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + multiScheduler.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerNoPreemptiveClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + scheduler0.close(); + scheduler1.close(); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6}, + 0, 2, timeFromNow(100))); + assertTrue(multiScheduler.waitNextEvent()); + scheduler2.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerSpamEvents() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals(msg[0], event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testSchedulerSpamEventsPullLater() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + } + + for (int i = 0; i < 1000; i++) { + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) i, event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testSchedulerSpamEventsCallbackLater() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + } + + for (int i = 0; i < 1000; i++) { + try { + assertTrue(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + } + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) i, event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testMultiSchedulerOutOfOrder() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3}, + 0, 1, + timeFromNow(400))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2}, + 0, 1, + timeFromNow(300))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1}, + 0, 1, + timeFromNow(200))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0}, + 0, 1, + timeFromNow(100))); + + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf0, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf1, event.data[0]); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf2, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf3, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testMultiSchedulerOutOfOrderNegativeTime() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3}, + 0, 1, + timeFromNow(-100))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2}, + 0, 1, + timeFromNow(-200))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1}, + 0, 1, + timeFromNow(-300))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0}, + 0, 1, + timeFromNow(-400))); + + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf0, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf1, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf2, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf3, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } +}