OSDN Git Service

Add in some more useful MonkeyRunner commands:
authorBill Napier <napier@google.com>
Wed, 9 Jun 2010 00:15:18 +0000 (17:15 -0700)
committerBill Napier <napier@google.com>
Thu, 10 Jun 2010 23:54:26 +0000 (16:54 -0700)
 - Add in device "drag" command to simulate a finger moving across the touch screen
 - Add in some pixel operation on MonkeyImage to scripts can do pixel level tests and comparisons.
 - Fix up the help command to no longer loop forever.
 - Start MonkeyRunner in interactive mode if no script file is specified.

Change-Id: Ifd2655d3291dcc9dc355d4c2efa9019dc38bee5d

12 files changed:
tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java
tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java
tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java
tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java
tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java [new file with mode: 0644]
tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java
tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java [new file with mode: 0644]

index 5ef7c36..258261b 100644 (file)
@@ -124,6 +124,26 @@ public final class JythonUtils {
     }
 
     /**
+     * Get a python floating point value from an ArgParser.
+     *
+     * @param ap the ArgParser to get the value from.
+     * @param position the position in the parser
+     * @param defaultValue the default value to return if the arg isn't specified.
+     * @return the double value
+     */
+    public static double getFloat(ArgParser ap, int position, double defaultValue) {
+        PyObject arg = ap.getPyObject(position, new PyFloat(defaultValue));
+
+        if (Py.isInstance(arg, PyFloat.TYPE)) {
+            return ((PyFloat) arg).asDouble();
+        }
+        if (Py.isInstance(arg, PyInteger.TYPE)) {
+            return ((PyInteger) arg).asDouble();
+        }
+        throw Py.TypeError("Unable to parse argument: " + position);
+    }
+
+    /**
      * Get a list of arguments from an ArgParser.
      *
      * @param ap the ArgParser
index f8cecc6..87c54c2 100644 (file)
@@ -22,9 +22,11 @@ import com.google.common.collect.Collections2;
 import com.android.monkeyrunner.doc.MonkeyRunnerExported;
 
 import org.python.core.ArgParser;
+import org.python.core.Py;
 import org.python.core.PyDictionary;
 import org.python.core.PyException;
 import org.python.core.PyObject;
+import org.python.core.PyTuple;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -112,6 +114,41 @@ public abstract class MonkeyDevice {
         touch(x, y, type);
     }
 
+    @MonkeyRunnerExported(doc = "Simulate a drag on the screen.",
+            args = { "start", "end", "duration", "steps"},
+            argDocs = { "The starting point for the drag (a tuple of x,y)",
+            "The end point for the drag (a tuple of x,y)",
+            "How long (in seconds) should the drag take (default is 1.0 seconds)",
+            "The number of steps to take when interpolating points. (default is 10)"})
+    public void drag(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        PyObject startObject = ap.getPyObject(0);
+        if (!(startObject instanceof PyTuple)) {
+            throw Py.TypeError("Agrument 0 is not a tuple");
+        }
+        PyObject endObject = ap.getPyObject(1);
+        if (!(endObject instanceof PyTuple)) {
+            throw Py.TypeError("Agrument 1 is not a tuple");
+        }
+
+        PyTuple start = (PyTuple) startObject;
+        PyTuple end = (PyTuple) endObject;
+
+        int startx = (Integer) start.__getitem__(0).__tojava__(Integer.class);
+        int starty = (Integer) start.__getitem__(1).__tojava__(Integer.class);
+        int endx = (Integer) end.__getitem__(0).__tojava__(Integer.class);
+        int endy = (Integer) end.__getitem__(1).__tojava__(Integer.class);
+
+        double seconds = JythonUtils.getFloat(ap, 2, 1.0);
+        long ms = (long) (seconds * 1000.0);
+
+        int steps = ap.getInt(3, 10);
+
+        drag(startx, starty, endx, endy, steps, ms);
+    }
+
     @MonkeyRunnerExported(doc = "Send a key press event to the specified button",
             args = { "name", "type" },
             argDocs = { "the name of the key to press", "the type of touch event to send"})
@@ -270,6 +307,7 @@ public abstract class MonkeyDevice {
         return JythonUtils.convertMapToDict(result);
     }
 
+    @MonkeyRunnerExported(doc = "Wake up the screen on the device")
     public void wake(PyObject[] args, String[] kws) {
         ArgParser ap = JythonUtils.createArgParser(args, kws);
         Preconditions.checkNotNull(ap);
@@ -288,6 +326,7 @@ public abstract class MonkeyDevice {
     protected abstract String getSystemProperty(String key);
     protected abstract void touch(int x, int y, TouchPressType type);
     protected abstract void press(String keyName, TouchPressType type);
+    protected abstract void drag(int startx, int starty, int endx, int endy, int steps, long ms);
     protected abstract void type(String string);
     protected abstract String shell(String cmd);
     protected abstract boolean installPackage(String path);
index aa96234..7475407 100644 (file)
@@ -20,12 +20,16 @@ import com.google.common.base.Preconditions;
 import com.android.monkeyrunner.doc.MonkeyRunnerExported;
 
 import org.python.core.ArgParser;
+import org.python.core.PyInteger;
 import org.python.core.PyObject;
+import org.python.core.PyTuple;
 
 import java.awt.Graphics;
 import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.util.Iterator;
 
 import javax.imageio.ImageIO;
@@ -36,13 +40,63 @@ import javax.imageio.stream.ImageOutputStream;
  * Jython object to encapsulate images that have been taken.
  */
 public abstract class MonkeyImage {
+    /**
+     * Convert the MonkeyImage into a BufferedImage.
+     *
+     * @return a BufferedImage for this MonkeyImage.
+     */
     public abstract BufferedImage createBufferedImage();
 
+    // Cache the BufferedImage so we don't have to generate it every time.
+    private WeakReference<BufferedImage> cachedBufferedImage = null;
+
+    /**
+     * Utility method to handle getting the BufferedImage and managing the cache.
+     *
+     * @return the BufferedImage for this image.
+     */
+    private BufferedImage getBufferedImage() {
+        // Check the cache first
+        if (cachedBufferedImage != null) {
+            BufferedImage img = cachedBufferedImage.get();
+            if (img != null) {
+                return img;
+            }
+        }
+
+        // Not in the cache, so create it and cache it.
+        BufferedImage img = createBufferedImage();
+        cachedBufferedImage = new WeakReference<BufferedImage>(img);
+        return img;
+    }
+
+    @MonkeyRunnerExported(doc = "Encode the image into a format and return the bytes.",
+        args = {"format"},
+        argDocs = { "The (optional) format in which to encode the image (PNG for example)" },
+        returns = "A String containing the bytes.")
+    public byte[] convertToBytes(PyObject[] args, String[] kws) {
+      ArgParser ap = JythonUtils.createArgParser(args, kws);
+      Preconditions.checkNotNull(ap);
+
+      String format = ap.getString(0, "png");
+
+      BufferedImage argb = convertSnapshot();
+
+      ByteArrayOutputStream os = new ByteArrayOutputStream();
+      try {
+          ImageIO.write(argb, format, os);
+      } catch (IOException e) {
+          return new byte[0];
+      }
+      return os.toByteArray();
+    }
+
     @MonkeyRunnerExported(doc = "Write out the file to the specified location.  If no " +
             "format is specified, this function tries to guess at the output format " +
             "depending on the file extension given.  If unable to determine, it uses PNG.",
             args = {"path", "format"},
-            argDocs = {"Where to write out the file"},
+            argDocs = {"Where to write out the file",
+                       "The format in which to encode the image (PNG for example)"},
             returns = "True if writing succeeded.")
     public boolean writeToFile(PyObject[] args, String[] kws) {
         ArgParser ap = JythonUtils.createArgParser(args, kws);
@@ -64,7 +118,7 @@ public abstract class MonkeyImage {
             return writeToFile(path, "png");
         }
         ImageWriter writer = writers.next();
-        BufferedImage image = createBufferedImage();
+        BufferedImage image = getBufferedImage();
         try {
             File f = new File(path);
             f.delete();
@@ -84,8 +138,44 @@ public abstract class MonkeyImage {
         return true;
     }
 
-    public boolean writeToFile(String path, String format) {
-        BufferedImage image = createBufferedImage();
+    @MonkeyRunnerExported(doc = "Get a single ARGB pixel from the image",
+            args = { "x", "y" },
+            argDocs = { "the x offset of the pixel", "the y offset of the pixel" },
+            returns = "A tuple of (A, R, G, B) for the pixel")
+    public PyObject getRawPixel(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        int x = ap.getInt(0);
+        int y = ap.getInt(1);
+        int pixel = getPixel(x, y);
+        PyInteger a = new PyInteger((pixel & 0xFF000000) >> 24);
+        PyInteger r = new PyInteger((pixel & 0x00FF0000) >> 16);
+        PyInteger g = new PyInteger((pixel & 0x0000FF00) >> 8);
+        PyInteger b = new PyInteger((pixel & 0x000000FF) >> 0);
+        return new PyTuple(a, r, g ,b);
+    }
+
+    @MonkeyRunnerExported(doc = "Get a single ARGB pixel from the image",
+            args = { "x", "y" },
+            argDocs = { "the x offset of the pixel", "the y offset of the pixel" },
+            returns = "An integer for the ARGB pixel")
+    public int getRawPixelInt(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        int x = ap.getInt(0);
+        int y = ap.getInt(1);
+        return getPixel(x, y);
+    }
+
+    private int getPixel(int x, int y) {
+        BufferedImage image = getBufferedImage();
+        return image.getRGB(x, y);
+    }
+
+    private BufferedImage convertSnapshot() {
+        BufferedImage image = getBufferedImage();
 
         // Convert the image to ARGB so ImageIO writes it out nicely
         BufferedImage argb = new BufferedImage(image.getWidth(), image.getHeight(),
@@ -93,6 +183,11 @@ public abstract class MonkeyImage {
         Graphics g = argb.createGraphics();
         g.drawImage(image, 0, 0, null);
         g.dispose();
+        return argb;
+    }
+
+    public boolean writeToFile(String path, String format) {
+        BufferedImage argb = convertSnapshot();
 
         try {
             ImageIO.write(argb, format, new File(path));
@@ -101,4 +196,72 @@ public abstract class MonkeyImage {
         }
         return true;
     }
-}
+
+    @MonkeyRunnerExported(doc = "Compare this image to the other image.",
+            args = {"other"},
+            argDocs = {"The other image."},
+            returns = "True if they are the same image.")
+    public boolean sameAs(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        PyObject otherObject = ap.getPyObject(0);
+        MonkeyImage other = (MonkeyImage) otherObject.__tojava__(MonkeyImage.class);
+
+        BufferedImage otherImage = other.getBufferedImage();
+        BufferedImage myImage = getBufferedImage();
+
+        // Easy size check
+        if (otherImage.getWidth() != myImage.getWidth()) {
+            return false;
+        }
+        if (otherImage.getHeight() != myImage.getHeight()) {
+            return false;
+        }
+
+        int[] otherPixel = new int[1];
+        int[] myPixel = new int[1];
+
+        // Now, go through pixel-by-pixel and check that the images are the same;
+        for (int y = 0; y < myImage.getHeight(); y++) {
+            for (int x = 0; x < myImage.getWidth(); x++) {
+                if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private static class BufferedImageMonkeyImage extends MonkeyImage {
+        private final BufferedImage image;
+
+        public BufferedImageMonkeyImage(BufferedImage image) {
+            this.image = image;
+        }
+
+        @Override
+        public BufferedImage createBufferedImage() {
+            return image;
+        }
+
+    }
+
+    @MonkeyRunnerExported(doc = "Get a sub-image of this image.",
+            args = {"rect"},
+            argDocs = {"A Tuple of (x, y, w, h) representing the area of the image to extract."},
+            returns = "The newly extracted image.")
+    public MonkeyImage getSubImage(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        PyTuple rect = (PyTuple) ap.getPyObjectByType(0, PyTuple.TYPE);
+        int x = rect.__getitem__(0).asInt();
+        int y = rect.__getitem__(1).asInt();
+        int w = rect.__getitem__(2).asInt();
+        int h = rect.__getitem__(3).asInt();
+
+        BufferedImage image = getBufferedImage();
+        return new BufferedImageMonkeyImage(image.getSubimage(x, y, w, h));
+    }
+}
\ No newline at end of file
index 65a5bcd..11a2dd4 100644 (file)
@@ -82,6 +82,18 @@ public class MonkeyManager {
     }
 
     /**
+     * Send a touch move event at the specified location.
+     *
+     * @param x the x coordinate of where to click
+     * @param y the y coordinate of where to click
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean touchMove(int x, int y) throws IOException {
+        return sendMonkeyEvent("touch move " + x + " " + y);
+    }
+
+    /**
      * Send a touch (down and then up) event at the specified location.
      *
      * @param x the x coordinate of where to click
index ffef5ed..bda4551 100644 (file)
@@ -97,7 +97,9 @@ public final class MonkeyRunnerHelp {
 
                 // Containing classes
                 for (Class<?> toAdd : clz.getClasses()) {
-                    newClasses.add(toAdd);
+                    if (haventSeen.apply(toAdd)) {
+                        newClasses.add(toAdd);
+                    }
                 }
             }
 
index 0586045..cf193c2 100644 (file)
@@ -95,7 +95,7 @@ public class MonkeyRunnerOptions {
 
         ImmutableList.Builder<File> pluginListBuilder = ImmutableList.builder();
         ImmutableList.Builder<String> argumentBuilder = ImmutableList.builder();
-        do {
+        while (index < args.length) {
             String argument = args[index++];
 
             if ("-s".equals(argument)) {
@@ -172,12 +172,7 @@ public class MonkeyRunnerOptions {
                     argumentBuilder.add(argument);
                 }
             }
-        } while (index < args.length);
-
-        if (scriptFile == null) {
-            printUsage("Missing required parameter");
-            return null;
-        }
+        };
 
         return new MonkeyRunnerOptions(hostname, port, scriptFile, backend,
                 pluginListBuilder.build(), argumentBuilder.build());
index 8c32408..1f539ba 100644 (file)
@@ -81,11 +81,16 @@ public class MonkeyRunnerStarter {
     private int run() {
         MonkeyRunner.setBackend(backend);
         Map<String, Predicate<PythonInterpreter>> plugins = handlePlugins();
-        int error = ScriptRunner.run(options.getScriptFile().getAbsolutePath(),
-            options.getArguments(), plugins);
-        backend.shutdown();
-        MonkeyRunner.setBackend(null);
-        return error;
+        if (options.getScriptFile() == null) {
+            ScriptRunner.console();
+            return 0;
+        } else {
+            int error = ScriptRunner.run(options.getScriptFile().getAbsolutePath(),
+                    options.getArguments(), plugins);
+            backend.shutdown();
+            MonkeyRunner.setBackend(null);
+            return error;
+        }
     }
 
     private Predicate<PythonInterpreter> handlePlugin(File f) {
index 616ba85..7b2edb0 100644 (file)
@@ -27,7 +27,6 @@ import org.python.util.InteractiveConsole;
 import org.python.util.PythonInterpreter;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -167,38 +166,11 @@ public class ScriptRunner {
     }
 
     /**
-     * Create and run a console using a new python interpreter for the test
-     * associated with this instance.
+     * Start an interactive python interpreter.
      */
-    public void console() throws IOException {
+    public static void console() {
         initPython();
         InteractiveConsole python = new InteractiveConsole();
-        initInterpreter(python, scope, variable);
         python.interact();
     }
-
-    /**
-     * Start an interactive python interpreter using the specified set of local
-     * variables. Use this to interrupt a running test script with a prompt:
-     *
-     * @param locals
-     */
-    public static void console(PyObject locals) {
-        initPython();
-        InteractiveConsole python = new InteractiveConsole(locals);
-        python.interact();
-    }
-
-    /**
-     * Initialize a python interpreter.
-     *
-     * @param python
-     * @param scope
-     * @throws IOException
-     */
-    public static void initInterpreter(PythonInterpreter python, Object scope, String variable)
-    throws IOException {
-        // Store the current test case as the this variable
-        python.set(variable, scope);
-    }
 }
index f71b160..78ee601 100644 (file)
@@ -23,6 +23,7 @@ import com.android.ddmlib.IDevice;
 import com.android.monkeyrunner.MonkeyDevice;
 import com.android.monkeyrunner.MonkeyImage;
 import com.android.monkeyrunner.MonkeyManager;
+import com.android.monkeyrunner.adb.LinearInterpolator.Point;
 
 import java.io.IOException;
 import java.net.InetAddress;
@@ -37,7 +38,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import java.util.regex.Pattern;
 
 import javax.annotation.Nullable;
 
@@ -421,4 +421,53 @@ public class AdbMonkeyDevice extends MonkeyDevice {
         }
         return map;
     }
+
+    @Override
+    protected void drag(int startx, int starty, int endx, int endy, int steps, long ms) {
+        final long iterationTime = ms / steps;
+
+        LinearInterpolator lerp = new LinearInterpolator(steps);
+        LinearInterpolator.Point start = new LinearInterpolator.Point(startx, starty);
+        LinearInterpolator.Point end = new LinearInterpolator.Point(endx, endy);
+        lerp.interpolate(start, end, new LinearInterpolator.Callback() {
+            @Override
+            public void step(Point point) {
+                try {
+                    manager.touchMove(point.getX(), point.getY());
+                } catch (IOException e) {
+                    LOG.log(Level.SEVERE, "Error sending drag start event", e);
+                }
+
+                try {
+                    Thread.sleep(iterationTime);
+                } catch (InterruptedException e) {
+                    LOG.log(Level.SEVERE, "Error sleeping", e);
+                }
+            }
+
+            @Override
+            public void start(Point point) {
+                try {
+                    manager.touchDown(point.getX(), point.getY());
+                } catch (IOException e) {
+                    LOG.log(Level.SEVERE, "Error sending drag start event", e);
+                }
+
+                try {
+                    Thread.sleep(iterationTime);
+                } catch (InterruptedException e) {
+                    LOG.log(Level.SEVERE, "Error sleeping", e);
+                }
+            }
+
+            @Override
+            public void end(Point point) {
+                try {
+                    manager.touchUp(point.getX(), point.getY());
+                } catch (IOException e) {
+                    LOG.log(Level.SEVERE, "Error sending drag end event", e);
+                }
+            }
+        });
+    }
 }
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java
new file mode 100644 (file)
index 0000000..e39fefd
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 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.monkeyrunner.adb;
+
+
+
+/**
+ * Linear Interpolation class.
+ */
+public class LinearInterpolator {
+    private final int steps;
+
+    /**
+     * Use our own Point class so we don't pull in java.awt.* just for this simple class.
+     */
+    public static class Point {
+        private final int x;
+        private final int y;
+
+        public Point(int x, int y) {
+            this.x = x;
+            this.y = y;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder().
+                append("(").
+                append(x).
+                append(",").
+                append(y).
+                append(")").toString();
+        }
+
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof Point) {
+                Point that = (Point) obj;
+                return this.x == that.x && this.y == that.y;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return 0x43125315 + x + y;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public int getY() {
+            return y;
+        }
+    }
+
+    /**
+     * Callback interface to recieve interpolated points.
+     */
+    public interface Callback {
+        /**
+         * Called once to inform of the start point.
+         */
+        void start(Point point);
+        /**
+         * Called once to inform of the end point.
+         */
+        void end(Point point);
+        /**
+         * Called at every step in-between start and end.
+         */
+        void step(Point point);
+    }
+
+    /**
+     * Create a new linear Interpolator.
+     *
+     * @param steps How many steps should be in a single run.  This counts the intervals
+     *              in-between points, so the actual number of points generated will be steps + 1.
+     */
+    public LinearInterpolator(int steps) {
+        this.steps = steps;
+    }
+
+    // Copied from android.util.MathUtils since we couldn't link it in on the host.
+    private static float lerp(float start, float stop, float amount) {
+        return start + (stop - start) * amount;
+    }
+
+    /**
+     * Calculate the interpolated points.
+     *
+     * @param start The starting point
+     * @param end The ending point
+     * @param callback the callback to call with each calculated points.
+     */
+    public void interpolate(Point start, Point end, Callback callback) {
+        int xDistance = Math.abs(end.getX() - start.getX());
+        int yDistance = Math.abs(end.getY() - start.getY());
+        float amount = (float) (1.0 / steps);
+
+
+        callback.start(start);
+        for (int i = 1; i < steps; i++) {
+            float newX = lerp(start.getX(), end.getX(), amount * i);
+            float newY = lerp(start.getY(), end.getY(), amount * i);
+
+            callback.step(new Point(Math.round(newX), Math.round(newY)));
+        }
+        // Generate final point
+        callback.end(end);
+    }
+}
index 645360e..f5c571d 100644 (file)
@@ -15,6 +15,8 @@
  */
 package com.android.monkeyrunner;
 
+import com.android.monkeyrunner.adb.LinearInterpolatorTest;
+
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestResult;
@@ -36,7 +38,7 @@ public class AllTests {
     public static void main(String args[]) {
         TestRunner tr = new TestRunner();
         TestResult result = tr.doRun(AllTests.suite(ImageUtilsTest.class, JythonUtilsTest.class,
-            MonkeyRunnerOptionsTest.class));
+            MonkeyRunnerOptionsTest.class, LinearInterpolatorTest.class));
         if (result.wasSuccessful()) {
             System.exit(0);
         } else {
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java
new file mode 100644 (file)
index 0000000..235550a
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2010 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.monkeyrunner.adb;
+
+import com.google.common.collect.Lists;
+
+import com.android.monkeyrunner.adb.LinearInterpolator.Point;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Unit tests for the LinerInterpolator class.S
+ */
+public class LinearInterpolatorTest extends TestCase {
+    private static class Collector implements LinearInterpolator.Callback {
+        private final List<LinearInterpolator.Point> points = Lists.newArrayList();
+
+        public List<LinearInterpolator.Point> getPoints() {
+            return points;
+        }
+
+        @Override
+        public void end(Point input) {
+            points.add(input);
+        }
+
+        @Override
+        public void start(Point input) {
+            points.add(input);
+        }
+
+        @Override
+        public void step(Point input) {
+            points.add(input);
+        }
+    }
+
+    List<Integer> STEP_POINTS = Lists.newArrayList(0, 100, 200, 300, 400, 500, 600, 700, 800, 900,
+            1000);
+    List<Integer> REVERSE_STEP_POINTS = Lists.newArrayList(1000, 900, 800, 700, 600, 500, 400, 300,
+            200, 100, 0);
+
+    public void testLerpRight() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(0, 100),
+                new LinearInterpolator.Point(1000, 100),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(STEP_POINTS.get(x), 100), points.get(x));
+        }
+    }
+
+    public void testLerpLeft() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(1000, 100),
+                new LinearInterpolator.Point(0, 100),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(REVERSE_STEP_POINTS.get(x), 100), points.get(x));
+        }
+    }
+
+    public void testLerpUp() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(100, 1000),
+                new LinearInterpolator.Point(100, 0),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(100, REVERSE_STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+
+    public void testLerpDown() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(100, 0),
+                new LinearInterpolator.Point(100, 1000),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(100, STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+
+    public void testLerpNW() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(0, 0),
+                new LinearInterpolator.Point(1000, 1000),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(STEP_POINTS.get(x), STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+
+    public void testLerpNE() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(1000, 1000),
+                new LinearInterpolator.Point(0, 0),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(REVERSE_STEP_POINTS.get(x), REVERSE_STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+}