Add ThreadStress test to try and exercise corner cases

Change-Id: I8d151e9ba935b8c76406483c6c276cc26896aabf
diff --git a/test/ThreadStress/ThreadStress.java b/test/ThreadStress/ThreadStress.java
new file mode 100644
index 0000000..94089dc
--- /dev/null
+++ b/test/ThreadStress/ThreadStress.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Run on host with:
+//   javac ThreadTest.java && java ThreadStress && rm *.class
+class ThreadStress implements Runnable {
+
+    public static final boolean DEBUG = true;
+
+    enum Operation {
+        OOM(1),
+        ALLOC(99),
+        EXIT(50),
+        WAIT(50);
+
+        private final int frequency;
+        Operation(int frequency) {
+            this.frequency = frequency;
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        final int numberOfThreads = 5;
+        final int totalOperations = 1000;
+        final int operationsPerThread = totalOperations/numberOfThreads;
+
+        // Lock used to notify threads performin Operation.WAIT
+        final Object lock = new Object();
+
+        // Each thread is going to do operationsPerThread
+        // operations. The distribution of operations is determined by
+        // the Operation.frequency values. We fill out an Operation[]
+        // for each thread with the operations it is to perform. The
+        // Operation[] is shuffled so that there is more random
+        // interactions between the threads.
+
+        // The simple-minded filling in of Operation[] based on
+        // Operation.frequency below won't have even have close to a
+        // reasonable distribution if the count of Operation
+        // frequencies is greater than the total number of
+        // operations. So here we do a quick sanity check in case
+        // people tweak the constants above.
+        int operationCount = 0;
+        for (Operation op : Operation.values()) {
+            operationCount += op.frequency;
+        }
+        if (operationCount > operationsPerThread) {
+            throw new AssertionError(operationCount + " > " + operationsPerThread);
+        }
+
+        // Fill in the Operation[] array for each thread by laying
+        // down references to operation according to their desired
+        // frequency.
+        final ThreadStress[] threadStresses = new ThreadStress[numberOfThreads];
+        for (int t = 0; t < threadStresses.length; t++) {
+            Operation[] operations = new Operation[operationsPerThread];
+            int o = 0;
+            LOOP:
+            while (true) {
+                for (Operation op : Operation.values()) {
+                    for (int f = 0; f < op.frequency; f++) {
+                        if (o == operations.length) {
+                            break LOOP;
+                        }
+                        operations[o] = op;
+                        o++;
+                    }
+                }
+            }
+            // Randomize the oepration order
+            Collections.shuffle(Arrays.asList(operations));
+            threadStresses[t] = new ThreadStress(lock, t, operations);
+        }
+
+        // Enable to dump operation counds per thread to make sure its
+        // sane compared to Operation.frequency
+        if (DEBUG) {
+            for (int t = 0; t < threadStresses.length; t++) {
+                Operation[] operations = new Operation[operationsPerThread];
+                Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
+                for (Operation operation : operations) {
+                    Integer ops = distribution.get(operation);
+                    if (ops == null) {
+                        ops = 1;
+                    } else {
+                        ops++;
+                    }
+                    distribution.put(operation, ops);
+                }
+                System.out.println("Distribution for " + t);
+                for (Operation op : Operation.values()) {
+                    System.out.println(op + " = " + distribution.get(op));
+                }
+            }
+        }
+
+        // Create the runners for each thread. The runner Thread
+        // ensures that thread that exit due to Operation.EXIT will be
+        // restarted until they reach their desired
+        // operationsPerThread.
+        Thread[] runners = new Thread[numberOfThreads];
+        for (int r = 0; r < runners.length; r++) {
+            final ThreadStress ts = threadStresses[r];
+            runners[r] = new Thread() {
+                final ThreadStress threadStress = ts;
+                public void run() {
+                    int id = threadStress.id;
+                    System.out.println("Starting runner for " + id);
+                    while (threadStress.nextOperation < operationsPerThread) {
+                        Thread thread = new Thread(ts);
+                        thread.start();
+                        try {
+                            thread.join();
+                        } catch (InterruptedException e) {
+                        }
+                        System.out.println("Thread exited for " + id + " with "
+                                           + (operationsPerThread - threadStress.nextOperation)
+                                           + " operations remaining.");
+                    }
+                    System.out.println("Finishing runner for " + id);
+                }
+            };
+        }
+
+        // The notifier thread is a daemon just loops forever to wake
+        // up threads in Operation.WAIT
+        Thread notifier = new Thread() {
+            public void run() {
+                while (true) {
+                    synchronized (lock) {
+                        lock.notifyAll();
+                    }
+                }
+            }
+        };
+        notifier.setDaemon(true);
+        notifier.start();
+
+        for (int r = 0; r < runners.length; r++) {
+            runners[r].start();
+        }
+        for (int r = 0; r < runners.length; r++) {
+            runners[r].join();
+        }
+    }
+
+    private final Operation[] operations;
+    private final Object lock;
+    private final int id;
+
+    private int nextOperation;
+
+    private ThreadStress(Object lock, int id, Operation[] operations) {
+        this.lock = lock;
+        this.id = id;
+        this.operations = operations;
+    }
+
+    public void run() {
+        try {
+            if (DEBUG) {
+                System.out.println("Starting ThreadStress " + id);
+            }
+            while (nextOperation < operations.length) {
+                Operation operation = operations[nextOperation];
+                if (DEBUG) {
+                    System.out.println("ThreadStress " + id
+                                       + " operation " + nextOperation
+                                       + " is " + operation);
+                }
+                nextOperation++;
+                switch (operation) {
+                    case EXIT: {
+                        return;
+                    }
+                    case WAIT: {
+                        synchronized (lock) {
+                            try {
+                                lock.wait();
+                            } catch (InterruptedException e) {
+                            }
+                        }
+                        break;
+                    }
+                    case OOM: {
+                        try {
+                            List<byte[]> l = new ArrayList<byte[]>();
+                            while (true) {
+                                l.add(new byte[1024]);
+                            }
+                        } catch (OutOfMemoryError e) {
+                        }
+                        break;
+                    }
+                    case ALLOC: {
+                        List<byte[]> l = new ArrayList<byte[]>();
+                        for (int i = 0; i < 1024; i++) {
+                            l.add(new byte[1024]);
+                        }
+                        break;
+                    }
+                    default: {
+                        throw new AssertionError(operation.toString());
+                    }
+                }
+            }
+        }
+        finally {
+            if (DEBUG) {
+                System.out.println("Finishing ThreadStress for " + id);
+            }
+        }
+    }
+}