increase coverage of SkPath.cpp, remove unused code

Using Mike Klein's excellent coverage tool, increase the
unit testing of SkPath.cpp from 70% to 95%.

Along the way, determined that these functions were not
maintained or used:

SkPath::pathTo
SkPath::contains

as well as a large block of SkPath::cheapGetDirection().

Changed SkPath::validate() to permit infinities in
the path data points.

Fixed errors in preserving direction.
Fixed error setting direction when convexity is unknown.

Added missing conic to moveTo only detector.

BUG=
R=bsalomon@google.com, reed@google.com

Author: caryclark@google.com

Review URL: https://codereview.chromium.org/65493004

git-svn-id: http://skia.googlecode.com/svn/trunk@12291 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp
index d8a2ca9..11151d9 100644
--- a/bench/PathBench.cpp
+++ b/bench/PathBench.cpp
@@ -495,7 +495,6 @@
         kAdd_AddType,
         kAddTrans_AddType,
         kAddMatrix_AddType,
-        kPathTo_AddType,
         kReverseAdd_AddType,
         kReversePathTo_AddType,
     };
@@ -513,8 +512,6 @@
                 return "path_add_path_trans";
             case kAddMatrix_AddType:
                 return "path_add_path_matrix";
-            case kPathTo_AddType:
-                return "path_path_to";
             case kReverseAdd_AddType:
                 return "path_reverse_add_path";
             case kReversePathTo_AddType:
@@ -526,9 +523,8 @@
     }
 
     virtual void onPreDraw() SK_OVERRIDE {
-        // pathTo and reversePathTo assume a single contour path.
-        bool allowMoves = kPathTo_AddType != fType &&
-                          kReversePathTo_AddType != fType;
+        // reversePathTo assumes a single contour path.
+        bool allowMoves = kReversePathTo_AddType != fType;
         this->createData(10, 100, allowMoves);
         fPaths0.reset(kPathCnt);
         fPaths1.reset(kPathCnt);
@@ -562,13 +558,6 @@
                     result.addPath(fPaths1[idx], fMatrix);
                 }
                 break;
-            case kPathTo_AddType:
-                for (int i = 0; i < this->getLoops(); ++i) {
-                    int idx = i & (kPathCnt - 1);
-                    SkPath result = fPaths0[idx];
-                    result.pathTo(fPaths1[idx]);
-                }
-                break;
             case kReverseAdd_AddType:
                 for (int i = 0; i < this->getLoops(); ++i) {
                     int idx = i & (kPathCnt - 1);
@@ -1036,7 +1025,6 @@
 DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kAdd_AddType); )
 DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kAddTrans_AddType); )
 DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kAddMatrix_AddType); )
-DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kPathTo_AddType); )
 DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kReverseAdd_AddType); )
 DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kReversePathTo_AddType); )
 
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index 2b111fe..ac4dd3b 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -972,11 +972,6 @@
     friend class Iter;
 
     friend class SkPathStroker;
-    /*  Append the first contour of path, ignoring path's initial point. If no
-        moveTo() call has been made for this contour, the first point is
-        automatically set to (0,0).
-    */
-    void pathTo(const SkPath& path);
 
     /*  Append, in reverse order, the first contour of path, ignoring path's
         last point. If no moveTo() call has been made for this contour, the
@@ -1019,7 +1014,8 @@
     friend class SkAutoPathBoundsUpdate;
     friend class SkAutoDisableOvalCheck;
     friend class SkAutoDisableDirectionCheck;
-    friend class SkBench_AddPathTest; // perf test pathTo/reversePathTo
+    friend class SkBench_AddPathTest; // perf test reversePathTo
+    friend class PathTest_Private; // unit test reversePathTo
 };
 
 #endif
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index f772717..5f53ce8 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -662,7 +662,7 @@
     SkPathRef::Editor ed(&fPathRef);
 
     // remember our index
-    fLastMoveToIndex = ed.pathRef()->countPoints();
+    fLastMoveToIndex = fPathRef->countPoints();
 
     ed.growForVerb(kMove_Verb)->set(x, y);
 }
@@ -1106,7 +1106,7 @@
         fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
 
         SkAutoPathBoundsUpdate apbu(this, bounds);
-        SkAutoDisableDirectionCheck(this);
+        SkAutoDisableDirectionCheck addc(this);
 
         this->incReserve(21);
         if (kCW_Direction == dir) {
@@ -1134,6 +1134,7 @@
     for (int i = 0; i < count; ++i) {
         if (*verbs == kLine_Verb ||
             *verbs == kQuad_Verb ||
+            *verbs == kConic_Verb ||
             *verbs == kCubic_Verb) {
             return false;
         }
@@ -1179,7 +1180,7 @@
     fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
 
     SkAutoPathBoundsUpdate apbu(this, rect);
-    SkAutoDisableDirectionCheck(this);
+    SkAutoDisableDirectionCheck addc(this);
 
     if (skip_hori) {
         rx = halfW;
@@ -1511,45 +1512,6 @@
     return gPtsInVerb[verb];
 }
 
-// ignore the initial moveto, and stop when the 1st contour ends
-void SkPath::pathTo(const SkPath& path) {
-    int i, vcount = path.fPathRef->countVerbs();
-    // exit early if the path is empty, or just has a moveTo.
-    if (vcount < 2) {
-        return;
-    }
-
-    SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
-
-    fIsOval = false;
-
-    const uint8_t* verbs = path.fPathRef->verbs();
-    // skip the initial moveTo
-    const SkPoint*  pts = path.fPathRef->points() + 1;
-    const SkScalar* conicWeight = path.fPathRef->conicWeights();
-
-    SkASSERT(verbs[~0] == kMove_Verb);
-    for (i = 1; i < vcount; i++) {
-        switch (verbs[~i]) {
-            case kLine_Verb:
-                this->lineTo(pts[0].fX, pts[0].fY);
-                break;
-            case kQuad_Verb:
-                this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
-                break;
-            case kConic_Verb:
-                this->conicTo(pts[0], pts[1], *conicWeight++);
-                break;
-            case kCubic_Verb:
-                this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
-                break;
-            case kClose_Verb:
-                return;
-        }
-        pts += pts_in_verb(verbs[~i]);
-    }
-}
-
 // ignore the last point of the 1st contour
 void SkPath::reversePathTo(const SkPath& path) {
     int i, vcount = path.fPathRef->countVerbs();
@@ -1755,6 +1717,7 @@
             } else if (det2x2 > 0) {
                 dst->fDirection = fDirection;
             } else {
+                dst->fConvexity = kUnknown_Convexity;
                 dst->fDirection = kUnknown_Direction;
             }
         }
@@ -2319,9 +2282,7 @@
     if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
         return false;
     }
-    if (sk_float_abs(compA) <= FLT_EPSILON && sk_float_abs(compB) <= FLT_EPSILON) {
-        return true;
-    }
+    // no need to check for small numbers because SkPath::Iter has removed degenerate values
     int aBits = SkFloatAs2sCompliment(compA);
     int bBits = SkFloatAs2sCompliment(compB);
     return aBits < bBits + epsilon && bBits < aBits + epsilon;
@@ -2632,64 +2593,7 @@
 }
 
 static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
-    if (dir) {
-        *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
-    }
-}
-
-#if 0
-#include "SkString.h"
-#include "../utils/SkParsePath.h"
-static void dumpPath(const SkPath& path) {
-    SkString str;
-    SkParsePath::ToSVGString(path, &str);
-    SkDebugf("%s\n", str.c_str());
-}
-#endif
-
-namespace {
-// for use with convex_dir_test
-double mul(double a, double b) { return a * b; }
-SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
-double toDouble(SkScalar a) { return SkScalarToDouble(a); }
-SkScalar toScalar(SkScalar a) { return a; }
-
-// determines the winding direction of a convex polygon with the precision
-// of T. CAST_SCALAR casts an SkScalar to T.
-template <typename T, T (CAST_SCALAR)(SkScalar)>
-bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
-    // we find the first three points that form a non-degenerate
-    // triangle. If there are no such points then the path is
-    // degenerate. The first is always point 0. Now we find the second
-    // point.
-    int i = 0;
-    enum { kX = 0, kY = 1 };
-    T v0[2];
-    while (1) {
-        v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
-        v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
-        if (v0[kX] || v0[kY]) {
-            break;
-        }
-        if (++i == n - 1) {
-            return false;
-        }
-    }
-    // now find a third point that is not colinear with the first two
-    // points and check the orientation of the triangle (which will be
-    // the same as the orientation of the path).
-    for (++i; i < n; ++i) {
-        T v1[2];
-        v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
-        v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
-        T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
-        if (0 != cross) {
-            *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
-            return true;
-        }
-    }
-    return false;
-}
+    *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
 }
 
 /*
@@ -2701,15 +2605,18 @@
  *  its cross product.
  */
 bool SkPath::cheapComputeDirection(Direction* dir) const {
-//    dumpPath(*this);
-    // don't want to pay the cost for computing this if it
-    // is unknown, so we don't call isConvex()
-
     if (kUnknown_Direction != fDirection) {
         *dir = static_cast<Direction>(fDirection);
         return true;
     }
-    const Convexity conv = this->getConvexityOrUnknown();
+
+    // don't want to pay the cost for computing this if it
+    // is unknown, so we don't call isConvex()
+    if (kConvex_Convexity == this->getConvexityOrUnknown()) {
+        SkASSERT(kUnknown_Direction == fDirection);
+        *dir = static_cast<Direction>(fDirection);
+        return false;
+    }
 
     ContourIter iter(*fPathRef.get());
 
@@ -2725,73 +2632,57 @@
 
         const SkPoint* pts = iter.pts();
         SkScalar cross = 0;
-        if (kConvex_Convexity == conv) {
-            // We try first at scalar precision, and then again at double
-            // precision. This is because the vectors computed between distant
-            // points may lose too much precision.
-            if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
-                fDirection = *dir;
-                return true;
+        int index = find_max_y(pts, n);
+        if (pts[index].fY < ymax) {
+            continue;
+        }
+
+        // If there is more than 1 distinct point at the y-max, we take the
+        // x-min and x-max of them and just subtract to compute the dir.
+        if (pts[(index + 1) % n].fY == pts[index].fY) {
+            int maxIndex;
+            int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
+            if (minIndex == maxIndex) {
+                goto TRY_CROSSPROD;
             }
-            if (convex_dir_test<double, toDouble>(n, pts, dir)) {
-                fDirection = *dir;
-                return true;
-            } else {
-                return false;
-            }
+            SkASSERT(pts[minIndex].fY == pts[index].fY);
+            SkASSERT(pts[maxIndex].fY == pts[index].fY);
+            SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
+            // we just subtract the indices, and let that auto-convert to
+            // SkScalar, since we just want - or + to signal the direction.
+            cross = minIndex - maxIndex;
         } else {
-            int index = find_max_y(pts, n);
-            if (pts[index].fY < ymax) {
+            TRY_CROSSPROD:
+            // Find a next and prev index to use for the cross-product test,
+            // but we try to find pts that form non-zero vectors from pts[index]
+            //
+            // Its possible that we can't find two non-degenerate vectors, so
+            // we have to guard our search (e.g. all the pts could be in the
+            // same place).
+
+            // we pass n - 1 instead of -1 so we don't foul up % operator by
+            // passing it a negative LH argument.
+            int prev = find_diff_pt(pts, index, n, n - 1);
+            if (prev == index) {
+                // completely degenerate, skip to next contour
                 continue;
             }
-
-            // If there is more than 1 distinct point at the y-max, we take the
-            // x-min and x-max of them and just subtract to compute the dir.
-            if (pts[(index + 1) % n].fY == pts[index].fY) {
-                int maxIndex;
-                int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
-                if (minIndex == maxIndex) {
-                    goto TRY_CROSSPROD;
-                }
-                SkASSERT(pts[minIndex].fY == pts[index].fY);
-                SkASSERT(pts[maxIndex].fY == pts[index].fY);
-                SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
-                // we just subtract the indices, and let that auto-convert to
-                // SkScalar, since we just want - or + to signal the direction.
-                cross = minIndex - maxIndex;
-            } else {
-                TRY_CROSSPROD:
-                // Find a next and prev index to use for the cross-product test,
-                // but we try to find pts that form non-zero vectors from pts[index]
-                //
-                // Its possible that we can't find two non-degenerate vectors, so
-                // we have to guard our search (e.g. all the pts could be in the
-                // same place).
-
-                // we pass n - 1 instead of -1 so we don't foul up % operator by
-                // passing it a negative LH argument.
-                int prev = find_diff_pt(pts, index, n, n - 1);
-                if (prev == index) {
-                    // completely degenerate, skip to next contour
-                    continue;
-                }
-                int next = find_diff_pt(pts, index, n, 1);
-                SkASSERT(next != index);
-                cross = cross_prod(pts[prev], pts[index], pts[next]);
-                // if we get a zero and the points are horizontal, then we look at the spread in
-                // x-direction. We really should continue to walk away from the degeneracy until
-                // there is a divergence.
-                if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
-                    // construct the subtract so we get the correct Direction below
-                    cross = pts[index].fX - pts[next].fX;
-                }
+            int next = find_diff_pt(pts, index, n, 1);
+            SkASSERT(next != index);
+            cross = cross_prod(pts[prev], pts[index], pts[next]);
+            // if we get a zero and the points are horizontal, then we look at the spread in
+            // x-direction. We really should continue to walk away from the degeneracy until
+            // there is a divergence.
+            if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
+                // construct the subtract so we get the correct Direction below
+                cross = pts[index].fX - pts[next].fX;
             }
+        }
 
-            if (cross) {
-                // record our best guess so far
-                ymax = pts[index].fY;
-                ymaxCross = cross;
-            }
+        if (cross) {
+            // record our best guess so far
+            ymax = pts[index].fY;
+            ymaxCross = cross;
         }
     }
     if (ymaxCross) {
@@ -2822,7 +2713,7 @@
 /*  Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
  t value such that cubic(t) = target
  */
-static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
+static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
                             SkScalar target, SkScalar* t) {
     //   SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
     SkASSERT(c0 < target && target < c3);
@@ -2851,7 +2742,6 @@
         }
     }
     *t = mid;
-    return true;
 }
 
 template <size_t N> static void find_minmax(const SkPoint pts[],
@@ -2893,13 +2783,9 @@
     }
 
     // compute the actual x(t) value
-    SkScalar t, xt;
-    if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
-        xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
-    } else {
-        SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
-        xt = y < mid ? pts[0].fX : pts[3].fX;
-    }
+    SkScalar t;
+    chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
+    SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
     return xt < x ? dir : 0;
 }
 
diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp
index 3557002..c66d75b 100644
--- a/src/core/SkPathRef.cpp
+++ b/src/core/SkPathRef.cpp
@@ -332,20 +332,19 @@
     SkASSERT(this->currSize() ==
                 fFreeSpace + sizeof(SkPoint) * fPointCnt + sizeof(uint8_t) * fVerbCnt);
 
-#ifdef SK_DEBUG
     if (!fBoundsIsDirty && !fBounds.isEmpty()) {
         bool isFinite = true;
         for (int i = 0; i < fPointCnt; ++i) {
-            SkASSERT(fBounds.fLeft - fPoints[i].fX   < SK_ScalarNearlyZero &&
+            SkASSERT(!fPoints[i].isFinite() || (
+                     fBounds.fLeft - fPoints[i].fX   < SK_ScalarNearlyZero &&
                      fPoints[i].fX - fBounds.fRight  < SK_ScalarNearlyZero &&
                      fBounds.fTop  - fPoints[i].fY   < SK_ScalarNearlyZero &&
-                     fPoints[i].fY - fBounds.fBottom < SK_ScalarNearlyZero);
+                     fPoints[i].fY - fBounds.fBottom < SK_ScalarNearlyZero));
             if (!fPoints[i].isFinite()) {
                 isFinite = false;
             }
         }
         SkASSERT(SkToBool(fIsFinite) == isFinite);
     }
-#endif
 }
 #endif
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 2dbf5c6..18d50d0 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -357,6 +357,33 @@
     surface->getCanvas()->drawPath(path, paint);
 }
 
+static void test_addrect(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.lineTo(0, 0);
+    path.addRect(SkRect::MakeWH(50, 100));
+    REPORTER_ASSERT(reporter, path.isRect(NULL));
+
+    path.reset();
+    path.lineTo(FLT_EPSILON, FLT_EPSILON);
+    path.addRect(SkRect::MakeWH(50, 100));
+    REPORTER_ASSERT(reporter, !path.isRect(NULL));
+
+    path.reset();
+    path.quadTo(0, 0, 0, 0);
+    path.addRect(SkRect::MakeWH(50, 100));
+    REPORTER_ASSERT(reporter, !path.isRect(NULL));
+
+    path.reset();
+    path.conicTo(0, 0, 0, 0, 0.5f);
+    path.addRect(SkRect::MakeWH(50, 100));
+    REPORTER_ASSERT(reporter, !path.isRect(NULL));
+
+    path.reset();
+    path.cubicTo(0, 0, 0, 0, 0, 0);
+    path.addRect(SkRect::MakeWH(50, 100));
+    REPORTER_ASSERT(reporter, !path.isRect(NULL));
+}
+
 // Make sure we stay non-finite once we get there (unless we reset or rewind).
 static void test_addrect_isfinite(skiatest::Reporter* reporter) {
     SkPath path;
@@ -811,6 +838,18 @@
     path.lineTo(-10 * SK_Scalar1, 60 * SK_Scalar1);
     check_direction(reporter, path, SkPath::kCCW_Direction);
 #endif
+
+    path.reset();
+    path.conicTo(20, 0, 20, 20, 0.5f);
+    path.close();
+    check_direction(reporter, path, SkPath::kCW_Direction);
+
+    path.reset();
+    path.lineTo(1, 1e7f);
+    path.lineTo(1e7f, 2e7f);
+    path.close();
+    REPORTER_ASSERT(reporter, SkPath::kConvex_Convexity == path.getConvexity());
+    check_direction(reporter, path, SkPath::kCCW_Direction);
 }
 
 static void add_rect(SkPath* path, const SkRect& r) {
@@ -1146,6 +1185,18 @@
         setFromString(&path, gRec[i].fPathStr);
         check_convexity(reporter, path, gRec[i].fExpectedConvexity);
         check_direction(reporter, path, gRec[i].fExpectedDirection);
+        // check after setting the initial convex and direction
+        if (kDontCheckDir != gRec[i].fExpectedDirection) {
+            SkPath copy(path);
+            SkPath::Direction dir;
+            bool foundDir = copy.cheapComputeDirection(&dir);
+            REPORTER_ASSERT(reporter, (gRec[i].fExpectedDirection == SkPath::kUnknown_Direction)
+                    ^ foundDir);
+            REPORTER_ASSERT(reporter, !foundDir || gRec[i].fExpectedDirection == dir);
+            check_convexity(reporter, copy, gRec[i].fExpectedConvexity);
+        }
+        REPORTER_ASSERT(reporter, gRec[i].fExpectedConvexity == path.getConvexity());
+        check_direction(reporter, path, gRec[i].fExpectedDirection);
     }
 }
 
@@ -1166,7 +1217,7 @@
 
     const SkScalar moveX = SkIntToScalar(1);
     const SkScalar moveY = SkIntToScalar(2);
-    SkASSERT(value != moveX && value != moveY);
+    REPORTER_ASSERT(reporter, value != moveX && value != moveY);
 
     path.moveTo(moveX, moveY);
     REPORTER_ASSERT(reporter, !path.isLine(NULL));
@@ -1177,7 +1228,7 @@
 
     const SkScalar lineX = SkIntToScalar(2);
     const SkScalar lineY = SkIntToScalar(2);
-    SkASSERT(value != lineX && value != lineY);
+    REPORTER_ASSERT(reporter, value != lineX && value != lineY);
 
     path.lineTo(lineX, lineY);
     REPORTER_ASSERT(reporter, path.isLine(NULL));
@@ -1193,6 +1244,10 @@
     REPORTER_ASSERT(reporter, !path.isLine(pts));
     REPORTER_ASSERT(reporter, pts[0].equals(moveX, moveY));
     REPORTER_ASSERT(reporter, pts[1].equals(lineX, lineY));
+
+    path.reset();
+    path.quadTo(1, 1, 2, 2);
+    REPORTER_ASSERT(reporter, !path.isLine(NULL));
 }
 
 static void test_conservativelyContains(skiatest::Reporter* reporter) {
@@ -1203,7 +1258,7 @@
 
     // A circle that bounds kBaseRect (with a significant amount of slop)
     SkScalar circleR = SkMaxScalar(kBaseRect.width(), kBaseRect.height());
-    circleR = SkScalarMul(circleR, SkFloatToScalar(1.75f)) / 2;
+    circleR = SkScalarMul(circleR, 1.75f) / 2;
     static const SkPoint kCircleC = {kBaseRect.centerX(), kBaseRect.centerY()};
 
     // round-rect radii
@@ -1214,74 +1269,75 @@
         bool   fInRect;
         bool   fInCircle;
         bool   fInRR;
+        bool   fInCubicRR;
     } kQueries[] = {
-        {kBaseRect, true, true, false},
+        {kBaseRect, true, true, false, false},
 
         // rect well inside of kBaseRect
         {SkRect::MakeLTRB(kBaseRect.fLeft + SkFloatToScalar(0.25f)*kBaseRect.width(),
                           kBaseRect.fTop + SkFloatToScalar(0.25f)*kBaseRect.height(),
                           kBaseRect.fRight - SkFloatToScalar(0.25f)*kBaseRect.width(),
                           kBaseRect.fBottom - SkFloatToScalar(0.25f)*kBaseRect.height()),
-                          true, true, true},
+                          true, true, true, true},
 
         // rects with edges off by one from kBaseRect's edges
         {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
                           kBaseRect.width(), kBaseRect.height() + 1),
-         false, true, false},
+         false, true, false, false},
         {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
                           kBaseRect.width() + 1, kBaseRect.height()),
-         false, true, false},
+         false, true, false, false},
         {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
                           kBaseRect.width() + 1, kBaseRect.height() + 1),
-         false, true, false},
+         false, true, false, false},
         {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
                           kBaseRect.width(), kBaseRect.height()),
-         false, true, false},
+         false, true, false, false},
         {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
                           kBaseRect.width(), kBaseRect.height()),
-         false, true, false},
+         false, true, false, false},
         {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
                           kBaseRect.width() + 2, kBaseRect.height()),
-         false, true, false},
+         false, true, false, false},
         {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
                           kBaseRect.width() + 2, kBaseRect.height()),
-         false, true, false},
+         false, true, false, false},
 
         // zero-w/h rects at each corner of kBaseRect
-        {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, 0, 0), true, true, false},
-        {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fTop, 0, 0), true, true, false},
-        {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fBottom, 0, 0), true, true, false},
-        {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fBottom, 0, 0), true, true, false},
+        {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, 0, 0), true, true, false, false},
+        {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fTop, 0, 0), true, true, false, true},
+        {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fBottom, 0, 0), true, true, false, true},
+        {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fBottom, 0, 0), true, true, false, true},
 
         // far away rect
         {SkRect::MakeXYWH(10 * kBaseRect.fRight, 10 * kBaseRect.fBottom,
                           SkIntToScalar(10), SkIntToScalar(10)),
-         false, false, false},
+         false, false, false, false},
 
         // very large rect containing kBaseRect
         {SkRect::MakeXYWH(kBaseRect.fLeft - 5 * kBaseRect.width(),
                           kBaseRect.fTop - 5 * kBaseRect.height(),
                           11 * kBaseRect.width(), 11 * kBaseRect.height()),
-         false, false, false},
+         false, false, false, false},
 
         // skinny rect that spans same y-range as kBaseRect
         {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
                           SkIntToScalar(1), kBaseRect.height()),
-         true, true, true},
+         true, true, true, true},
 
         // short rect that spans same x-range as kBaseRect
         {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(), kBaseRect.width(), SkScalar(1)),
-         true, true, true},
+         true, true, true, true},
 
         // skinny rect that spans slightly larger y-range than kBaseRect
         {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
                           SkIntToScalar(1), kBaseRect.height() + 1),
-         false, true, false},
+         false, true, false, false},
 
         // short rect that spans slightly larger x-range than kBaseRect
         {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(),
                           kBaseRect.width() + 1, SkScalar(1)),
-         false, true, false},
+         false, true, false, false},
     };
 
     for (int inv = 0; inv < 4; ++inv) {
@@ -1309,6 +1365,19 @@
                 path.addRoundRect(kBaseRect, kRRRadii[0], kRRRadii[1], dir);
                 REPORTER_ASSERT(reporter, kQueries[q].fInRR ==
                                           path.conservativelyContainsRect(qRect));
+
+                path.reset();
+                path.moveTo(kBaseRect.fLeft + kRRRadii[0], kBaseRect.fTop);
+                path.cubicTo(kBaseRect.fLeft + kRRRadii[0] / 2, kBaseRect.fTop,
+                             kBaseRect.fLeft, kBaseRect.fTop + kRRRadii[1] / 2,
+                             kBaseRect.fLeft, kBaseRect.fTop + kRRRadii[1]);
+                path.lineTo(kBaseRect.fLeft, kBaseRect.fBottom);
+                path.lineTo(kBaseRect.fRight, kBaseRect.fBottom);
+                path.lineTo(kBaseRect.fRight, kBaseRect.fTop);
+                path.close();
+                REPORTER_ASSERT(reporter, kQueries[q].fInCubicRR ==
+                                          path.conservativelyContainsRect(qRect));
+
             }
             // Slightly non-convex shape, shouldn't contain any rects.
             path.reset();
@@ -1361,6 +1430,9 @@
                                                                                SkIntToScalar(10),
                                                                                SkIntToScalar(10))));
 
+    path.reset();
+    path.lineTo(100, 100);
+    REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(0, 0, 1, 1)));
 }
 
 static void test_isRect_open_close(skiatest::Reporter* reporter) {
@@ -1818,6 +1890,13 @@
     REPORTER_ASSERT(reporter, size1 == size3);
     REPORTER_ASSERT(reporter, p == p2);
 
+    size3 = p2.readFromMemory(buffer, 0);
+    REPORTER_ASSERT(reporter, !size3);
+
+    SkPath tooShort;
+    size3 = tooShort.readFromMemory(buffer, size1 - 1);
+    REPORTER_ASSERT(reporter, tooShort.isEmpty());
+
     char buffer2[1024];
     size3 = p2.writeToMemory(buffer2);
     REPORTER_ASSERT(reporter, size1 == size3);
@@ -1836,17 +1915,25 @@
 static void test_transform(skiatest::Reporter* reporter) {
     SkPath p, p1;
 
+#define CONIC_PERSPECTIVE_BUG_FIXED 0
     static const SkPoint pts[] = {
-        { 0, 0 },
-        { SkIntToScalar(10), SkIntToScalar(10) },
-        { SkIntToScalar(20), SkIntToScalar(10) }, { SkIntToScalar(20), 0 },
-        { 0, 0 }, { 0, SkIntToScalar(10) }, { SkIntToScalar(1), SkIntToScalar(10) }
+        { 0, 0 },  // move
+        { SkIntToScalar(10), SkIntToScalar(10) },  // line
+        { SkIntToScalar(20), SkIntToScalar(10) }, { SkIntToScalar(20), 0 },  // quad
+        { 0, 0 }, { 0, SkIntToScalar(10) }, { SkIntToScalar(1), SkIntToScalar(10) },  // cubic
+#if CONIC_PERSPECTIVE_BUG_FIXED
+        { 0, 0 }, { SkIntToScalar(20), SkIntToScalar(10) },  // conic
+#endif
     };
+    const int kPtCount = SK_ARRAY_COUNT(pts);
     p.moveTo(pts[0]);
     p.lineTo(pts[1]);
     p.quadTo(pts[2], pts[3]);
     p.cubicTo(pts[4], pts[5], pts[6]);
-
+#if CONIC_PERSPECTIVE_BUG_FIXED
+    p.conicTo(pts[4], pts[5], 0.5f);
+#endif
+    p.close();
     SkMatrix matrix;
     matrix.reset();
     p.transform(matrix, &p1);
@@ -1854,13 +1941,36 @@
 
     matrix.setScale(SK_Scalar1 * 2, SK_Scalar1 * 3);
     p.transform(matrix, &p1);
-    SkPoint pts1[7];
-    int count = p1.getPoints(pts1, 7);
-    REPORTER_ASSERT(reporter, 7 == count);
+    SkPoint pts1[kPtCount];
+    int count = p1.getPoints(pts1, kPtCount);
+    REPORTER_ASSERT(reporter, kPtCount == count);
     for (int i = 0; i < count; ++i) {
         SkPoint newPt = SkPoint::Make(pts[i].fX * 2, pts[i].fY * 3);
         REPORTER_ASSERT(reporter, newPt == pts1[i]);
     }
+    matrix.reset();
+    matrix.setPerspX(SkScalarToPersp(4));
+    p.transform(matrix, &p1);
+    REPORTER_ASSERT(reporter, matrix.invert(&matrix));
+    p1.transform(matrix, NULL);
+    SkRect pBounds = p.getBounds();
+    SkRect p1Bounds = p1.getBounds();
+    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fLeft, p1Bounds.fLeft));
+    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fTop, p1Bounds.fTop));
+    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fRight, p1Bounds.fRight));
+    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fBottom, p1Bounds.fBottom));
+
+    matrix.reset();
+    p.reset();
+    p.addCircle(0, 0, 1, SkPath::kCW_Direction);
+    p.transform(matrix, &p1);
+    REPORTER_ASSERT(reporter, p1.cheapIsDirection(SkPath::kCW_Direction));
+    matrix.setScaleX(-1);
+    p.transform(matrix, &p1);
+    REPORTER_ASSERT(reporter, p1.cheapIsDirection(SkPath::kCCW_Direction));
+    matrix.setAll(1, 1, 0, 1, 1, 0, 0, 0, 1);
+    p.transform(matrix, &p1);
+    REPORTER_ASSERT(reporter, p1.cheapIsDirection(SkPath::kUnknown_Direction));
 }
 
 static void test_zero_length_paths(skiatest::Reporter* reporter) {
@@ -2064,6 +2174,71 @@
         REPORTER_ASSERT(reporter, j == (int)gIterTests[i].numResultVerbs);
     }
 
+    p.reset();
+    iter.setPath(p, false);
+    REPORTER_ASSERT(reporter, !iter.isClosedContour());
+    p.lineTo(1, 1);
+    p.close();
+    iter.setPath(p, false);
+    REPORTER_ASSERT(reporter, iter.isClosedContour());
+    p.reset();
+    iter.setPath(p, true);
+    REPORTER_ASSERT(reporter, !iter.isClosedContour());
+    p.lineTo(1, 1);
+    iter.setPath(p, true);
+    REPORTER_ASSERT(reporter, iter.isClosedContour());
+    p.moveTo(0, 0);
+    p.lineTo(2, 2);
+    iter.setPath(p, false);
+    REPORTER_ASSERT(reporter, !iter.isClosedContour());
+
+    // this checks to see if the NaN logic is executed in SkPath::autoClose(), but does not
+    // check to see if the result is correct.
+    for (int setNaN = 0; setNaN < 4; ++setNaN) {
+        p.reset();
+        p.moveTo(setNaN == 0 ? SK_ScalarNaN : 0, setNaN == 1 ? SK_ScalarNaN : 0);
+        p.lineTo(setNaN == 2 ? SK_ScalarNaN : 1, setNaN == 3 ? SK_ScalarNaN : 1);
+        iter.setPath(p, true);
+        iter.next(pts, false);
+        iter.next(pts, false);
+        REPORTER_ASSERT(reporter, SkPath::kClose_Verb == iter.next(pts, false));
+    }
+
+    p.reset();
+    p.quadTo(0, 0, 0, 0);
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kQuad_Verb == iter.next(pts, false));
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true));
+
+    p.reset();
+    p.conicTo(0, 0, 0, 0, 0.5f);
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kConic_Verb == iter.next(pts, false));
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true));
+
+    p.reset();
+    p.cubicTo(0, 0, 0, 0, 0, 0);
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kCubic_Verb == iter.next(pts, false));
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true));
+
+    p.moveTo(1, 1);  // add a trailing moveto
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kCubic_Verb == iter.next(pts, false));
+    iter.setPath(p, false);
+    iter.next(pts, false);
+    REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true));
+
     // The GM degeneratesegments.cpp test is more extensive
 }
 
@@ -2323,7 +2498,7 @@
     if (SkPath::kCCW_Direction == dir) {
         dir = SkPath::kCW_Direction;
     } else {
-        SkASSERT(SkPath::kCW_Direction == dir);
+        REPORTER_ASSERT(reporter, SkPath::kCW_Direction == dir);
         dir = SkPath::kCCW_Direction;
     }
     check_for_circle(reporter, tmp, false, dir);
@@ -2381,7 +2556,7 @@
     if (SkPath::kCW_Direction == dir) {
         dir = SkPath::kCCW_Direction;
     } else {
-        SkASSERT(SkPath::kCCW_Direction == dir);
+        REPORTER_ASSERT(reporter, SkPath::kCCW_Direction == dir);
         dir = SkPath::kCW_Direction;
     }
 
@@ -2400,7 +2575,7 @@
     if (SkPath::kCW_Direction == dir) {
         dir = SkPath::kCCW_Direction;
     } else {
-        SkASSERT(SkPath::kCCW_Direction == dir);
+        REPORTER_ASSERT(reporter, SkPath::kCCW_Direction == dir);
         dir = SkPath::kCW_Direction;
     }
 
@@ -2502,6 +2677,11 @@
     check_for_circle(reporter, path, false, SkPath::kCW_Direction);
 
     test_circle_with_add_paths(reporter);
+
+    // test negative radius
+    path.reset();
+    path.addCircle(0, 0, -1, SkPath::kCW_Direction);
+    REPORTER_ASSERT(reporter, path.isEmpty());
 }
 
 static void test_oval(skiatest::Reporter* reporter) {
@@ -2573,8 +2753,10 @@
     REPORTER_ASSERT(reporter, !(p != empty));
 }
 
-static void test_rrect_is_convex(skiatest::Reporter* reporter, SkPath* path) {
+static void test_rrect_is_convex(skiatest::Reporter* reporter, SkPath* path,
+                                 SkPath::Direction dir) {
     REPORTER_ASSERT(reporter, path->isConvex());
+    REPORTER_ASSERT(reporter, path->cheapIsDirection(dir));
     path->setConvexity(SkPath::kUnknown_Convexity);
     REPORTER_ASSERT(reporter, path->isConvex());
     path->reset();
@@ -2587,19 +2769,373 @@
     SkRect r = {10, 20, 30, 40};
     rr.setRectRadii(r, radii);
     p.addRRect(rr);
-    test_rrect_is_convex(reporter, &p);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
     p.addRRect(rr, SkPath::kCCW_Direction);
-    test_rrect_is_convex(reporter, &p);
+    test_rrect_is_convex(reporter, &p, SkPath::kCCW_Direction);
     p.addRoundRect(r, &radii[0].fX);
-    test_rrect_is_convex(reporter, &p);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
     p.addRoundRect(r, &radii[0].fX, SkPath::kCCW_Direction);
-    test_rrect_is_convex(reporter, &p);
+    test_rrect_is_convex(reporter, &p, SkPath::kCCW_Direction);
     p.addRoundRect(r, radii[1].fX, radii[1].fY);
-    test_rrect_is_convex(reporter, &p);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
     p.addRoundRect(r, radii[1].fX, radii[1].fY, SkPath::kCCW_Direction);
-    test_rrect_is_convex(reporter, &p);
+    test_rrect_is_convex(reporter, &p, SkPath::kCCW_Direction);
+    for (size_t i = 0; i < SK_ARRAY_COUNT(radii); ++i) {
+        SkVector save = radii[i];
+        radii[i].set(0, 0);
+        rr.setRectRadii(r, radii);
+        p.addRRect(rr);
+        test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
+        radii[i] = save;
+    }
+    p.addRoundRect(r, 0, 0);
+    SkRect returnedRect;
+    REPORTER_ASSERT(reporter, p.isRect(&returnedRect));
+    REPORTER_ASSERT(reporter, returnedRect == r);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
+    SkVector zeroRadii[] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
+    rr.setRectRadii(r, zeroRadii);
+    p.addRRect(rr);
+    bool closed;
+    SkPath::Direction dir;
+    REPORTER_ASSERT(reporter, p.isRect(&closed, &dir));
+    REPORTER_ASSERT(reporter, closed);
+    REPORTER_ASSERT(reporter, SkPath::kCW_Direction == dir);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
+    p.addRRect(rr, SkPath::kCW_Direction);
+    p.addRRect(rr, SkPath::kCW_Direction);
+    REPORTER_ASSERT(reporter, !p.isConvex());
+    p.reset();
+    p.addRRect(rr, SkPath::kCCW_Direction);
+    p.addRRect(rr, SkPath::kCCW_Direction);
+    REPORTER_ASSERT(reporter, !p.isConvex());
+    p.reset();
+    SkRect emptyR = {10, 20, 10, 30};
+    rr.setRectRadii(emptyR, radii);
+    p.addRRect(rr);
+    REPORTER_ASSERT(reporter, p.isEmpty());
+    SkRect largeR = {0, 0, SK_ScalarMax, SK_ScalarMax};
+    rr.setRectRadii(largeR, radii);
+    p.addRRect(rr);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
+    SkRect infR = {0, 0, SK_ScalarMax, SK_ScalarInfinity};
+    rr.setRectRadii(infR, radii);
+    p.addRRect(rr);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
+    SkRect tinyR = {0, 0, 1e-9f, 1e-9f};
+    p.addRoundRect(tinyR, 5e-11f, 5e-11f);
+    test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
 }
 
+static void test_arc(skiatest::Reporter* reporter) {
+    SkPath p;
+    SkRect emptyOval = {10, 20, 30, 20};
+    REPORTER_ASSERT(reporter, emptyOval.isEmpty());
+    p.addArc(emptyOval, 1, 2);
+    REPORTER_ASSERT(reporter, p.isEmpty());
+    p.reset();
+    SkRect oval = {10, 20, 30, 40};
+    p.addArc(oval, 1, 0);
+    REPORTER_ASSERT(reporter, p.isEmpty());
+    p.reset();
+    SkPath cwOval;
+    cwOval.addOval(oval);
+    p.addArc(oval, 1, 360);
+    REPORTER_ASSERT(reporter, p == cwOval);
+    p.reset();
+    SkPath ccwOval;
+    ccwOval.addOval(oval, SkPath::kCCW_Direction);
+    p.addArc(oval, 1, -360);
+    REPORTER_ASSERT(reporter, p == ccwOval);
+    p.reset();
+    p.addArc(oval, 1, 180);
+    REPORTER_ASSERT(reporter, p.isConvex());
+    REPORTER_ASSERT(reporter, p.cheapIsDirection(SkPath::kCW_Direction));
+    p.setConvexity(SkPath::kUnknown_Convexity);
+    REPORTER_ASSERT(reporter, p.isConvex());
+}
+
+static void check_move(skiatest::Reporter* reporter, SkPath::RawIter* iter,
+                       SkScalar x0, SkScalar y0) {
+    SkPoint pts[4];
+    SkPath::Verb v = iter->next(pts);
+    REPORTER_ASSERT(reporter, v == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == x0);
+    REPORTER_ASSERT(reporter, pts[0].fY == y0);
+}
+
+static void check_line(skiatest::Reporter* reporter, SkPath::RawIter* iter,
+                       SkScalar x1, SkScalar y1) {
+    SkPoint pts[4];
+    SkPath::Verb v = iter->next(pts);
+    REPORTER_ASSERT(reporter, v == SkPath::kLine_Verb);
+    REPORTER_ASSERT(reporter, pts[1].fX == x1);
+    REPORTER_ASSERT(reporter, pts[1].fY == y1);
+}
+
+static void check_quad(skiatest::Reporter* reporter, SkPath::RawIter* iter,
+                       SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+    SkPoint pts[4];
+    SkPath::Verb v = iter->next(pts);
+    REPORTER_ASSERT(reporter, v == SkPath::kQuad_Verb);
+    REPORTER_ASSERT(reporter, pts[1].fX == x1);
+    REPORTER_ASSERT(reporter, pts[1].fY == y1);
+    REPORTER_ASSERT(reporter, pts[2].fX == x2);
+    REPORTER_ASSERT(reporter, pts[2].fY == y2);
+}
+
+static void check_done(skiatest::Reporter* reporter, SkPath* p, SkPath::RawIter* iter) {
+    SkPoint pts[4];
+    SkPath::Verb v = iter->next(pts);
+    REPORTER_ASSERT(reporter, v == SkPath::kDone_Verb);
+}
+
+static void check_done_and_reset(skiatest::Reporter* reporter, SkPath* p, SkPath::RawIter* iter) {
+    check_done(reporter, p, iter);
+    p->reset();
+}
+
+static void check_path_is_move_and_reset(skiatest::Reporter* reporter, SkPath* p,
+                                         SkScalar x0, SkScalar y0) {
+    SkPath::RawIter iter(*p);
+    check_move(reporter, &iter, x0, y0);
+    check_done_and_reset(reporter, p, &iter);
+}
+
+static void check_path_is_line_and_reset(skiatest::Reporter* reporter, SkPath* p,
+                                         SkScalar x1, SkScalar y1) {
+    SkPath::RawIter iter(*p);
+    check_move(reporter, &iter, 0, 0);
+    check_line(reporter, &iter, x1, y1);
+    check_done_and_reset(reporter, p, &iter);
+}
+
+static void check_path_is_line(skiatest::Reporter* reporter, SkPath* p,
+                                         SkScalar x1, SkScalar y1) {
+    SkPath::RawIter iter(*p);
+    check_move(reporter, &iter, 0, 0);
+    check_line(reporter, &iter, x1, y1);
+    check_done(reporter, p, &iter);
+}
+
+static void check_path_is_line_pair_and_reset(skiatest::Reporter* reporter, SkPath* p,
+                                    SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+    SkPath::RawIter iter(*p);
+    check_move(reporter, &iter, 0, 0);
+    check_line(reporter, &iter, x1, y1);
+    check_line(reporter, &iter, x2, y2);
+    check_done_and_reset(reporter, p, &iter);
+}
+
+static void check_path_is_quad_and_reset(skiatest::Reporter* reporter, SkPath* p,
+                                    SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+    SkPath::RawIter iter(*p);
+    check_move(reporter, &iter, 0, 0);
+    check_quad(reporter, &iter, x1, y1, x2, y2);
+    check_done_and_reset(reporter, p, &iter);
+}
+
+static void test_arcTo(skiatest::Reporter* reporter) {
+    SkPath p;
+    p.arcTo(0, 0, 1, 2, 1);
+    check_path_is_line_and_reset(reporter, &p, 0, 0);
+    p.arcTo(1, 2, 1, 2, 1);
+    check_path_is_line_and_reset(reporter, &p, 1, 2);
+    p.arcTo(1, 2, 3, 4, 0);
+    check_path_is_line_and_reset(reporter, &p, 1, 2);
+    p.arcTo(1, 2, 0, 0, 1);
+    check_path_is_line_and_reset(reporter, &p, 1, 2);
+    p.arcTo(1, 0, 1, 1, 1);
+    SkPoint pt;
+    REPORTER_ASSERT(reporter, p.getLastPt(&pt) && pt.fX == 1 && pt.fY == 1);
+    p.reset();
+    p.arcTo(1, 0, 1, -1, 1);
+    REPORTER_ASSERT(reporter, p.getLastPt(&pt) && pt.fX == 1 && pt.fY == -1);
+    p.reset();
+    SkRect oval = {1, 2, 3, 4};
+    p.arcTo(oval, 0, 0, true);
+    check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY());
+    p.arcTo(oval, 0, 0, false);
+    check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY());
+    p.arcTo(oval, 360, 0, true);
+    check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY());
+    p.arcTo(oval, 360, 0, false);
+    check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY());
+    for (float sweep = 359, delta = 0.5f; sweep != (float) (sweep + delta); ) {
+        p.arcTo(oval, 0, SkFloatToScalar(sweep), false);
+        REPORTER_ASSERT(reporter, p.getBounds() == oval);
+        sweep += delta;
+        delta /= 2;
+    }
+    for (float sweep = 361, delta = 0.5f; sweep != (float) (sweep - delta);) {
+        p.arcTo(oval, 0, SkFloatToScalar(sweep), false);
+        REPORTER_ASSERT(reporter, p.getBounds() == oval);
+        sweep -= delta;
+        delta /= 2;
+    }
+    SkRect noOvalWidth = {1, 2, 0, 3};
+    p.reset();
+    p.arcTo(noOvalWidth, 0, 360, false);
+    REPORTER_ASSERT(reporter, p.isEmpty());
+
+    SkRect noOvalHeight = {1, 2, 3, 1};
+    p.reset();
+    p.arcTo(noOvalHeight, 0, 360, false);
+    REPORTER_ASSERT(reporter, p.isEmpty());
+}
+
+static void test_addPath(skiatest::Reporter* reporter) {
+    SkPath p, q;
+    p.lineTo(1, 2);
+    q.moveTo(4, 4);
+    q.lineTo(7, 8);
+    q.conicTo(8, 7, 6, 5, 0.5f);
+    q.quadTo(6, 7, 8, 6);
+    q.cubicTo(5, 6, 7, 8, 7, 5);
+    q.close();
+    p.addPath(q, -4, -4);
+    SkRect expected = {0, 0, 4, 4};
+    REPORTER_ASSERT(reporter, p.getBounds() == expected);
+    p.reset();
+    p.reverseAddPath(q);
+    SkRect reverseExpected = {4, 4, 8, 8};
+    REPORTER_ASSERT(reporter, p.getBounds() == reverseExpected);
+}
+
+static void test_conicTo_special_case(skiatest::Reporter* reporter) {
+    SkPath p;
+    p.conicTo(1, 2, 3, 4, -1);
+    check_path_is_line_and_reset(reporter, &p, 3, 4);
+    p.conicTo(1, 2, 3, 4, SK_ScalarInfinity);
+    check_path_is_line_pair_and_reset(reporter, &p, 1, 2, 3, 4);
+    p.conicTo(1, 2, 3, 4, 1);
+    check_path_is_quad_and_reset(reporter, &p, 1, 2, 3, 4);
+}
+
+static void test_get_point(skiatest::Reporter* reporter) {
+    SkPath p;
+    SkPoint pt = p.getPoint(0);
+    REPORTER_ASSERT(reporter, pt == SkPoint::Make(0, 0));
+    REPORTER_ASSERT(reporter, !p.getLastPt(NULL));
+    REPORTER_ASSERT(reporter, !p.getLastPt(&pt) && pt == SkPoint::Make(0, 0));
+    p.setLastPt(10, 10);
+    pt = p.getPoint(0);
+    REPORTER_ASSERT(reporter, pt == SkPoint::Make(10, 10));
+    REPORTER_ASSERT(reporter, p.getLastPt(NULL));
+    p.rMoveTo(10, 10);
+    REPORTER_ASSERT(reporter, p.getLastPt(&pt) && pt == SkPoint::Make(20, 20));
+}
+
+static void test_contains(skiatest::Reporter* reporter) {
+    SkPath p;
+    p.setFillType(SkPath::kInverseWinding_FillType);
+    REPORTER_ASSERT(reporter, p.contains(0, 0));
+    p.setFillType(SkPath::kWinding_FillType);
+    REPORTER_ASSERT(reporter, !p.contains(0, 0));
+    p.moveTo(4, 4);
+    p.lineTo(6, 8);
+    p.lineTo(8, 4);
+    // test quick reject
+    REPORTER_ASSERT(reporter, !p.contains(4, 0));
+    REPORTER_ASSERT(reporter, !p.contains(0, 4));
+    REPORTER_ASSERT(reporter, !p.contains(4, 10));
+    REPORTER_ASSERT(reporter, !p.contains(10, 4));
+    // test various crossings in x
+    REPORTER_ASSERT(reporter, !p.contains(5, 7));
+    REPORTER_ASSERT(reporter, p.contains(6, 7));
+    REPORTER_ASSERT(reporter, !p.contains(7, 7));
+    p.reset();
+    p.moveTo(4, 4);
+    p.lineTo(8, 6);
+    p.lineTo(4, 8);
+    // test various crossings in y
+    REPORTER_ASSERT(reporter, !p.contains(7, 5));
+    REPORTER_ASSERT(reporter, p.contains(7, 6));
+    REPORTER_ASSERT(reporter, !p.contains(7, 7));
+    // test quads
+    p.reset();
+    p.moveTo(4, 4);
+    p.quadTo(6, 6, 8, 8);
+    p.quadTo(6, 8, 4, 8);
+    p.quadTo(4, 6, 4, 4);
+    REPORTER_ASSERT(reporter, p.contains(5, 6));
+    REPORTER_ASSERT(reporter, !p.contains(6, 5));
+
+    p.reset();
+    p.moveTo(6, 6);
+    p.quadTo(8, 8, 6, 8);
+    p.quadTo(4, 8, 4, 6);
+    p.quadTo(4, 4, 6, 6);
+    REPORTER_ASSERT(reporter, p.contains(5, 6));
+    REPORTER_ASSERT(reporter, !p.contains(6, 5));
+
+#define CONIC_CONTAINS_BUG_FIXED 0
+#if CONIC_CONTAINS_BUG_FIXED
+    p.reset();
+    p.moveTo(4, 4);
+    p.conicTo(6, 6, 8, 8, 0.5f);
+    p.conicTo(6, 8, 4, 8, 0.5f);
+    p.conicTo(4, 6, 4, 4, 0.5f);
+    REPORTER_ASSERT(reporter, p.contains(5, 6));
+    REPORTER_ASSERT(reporter, !p.contains(6, 5));
+#endif
+
+    // test cubics
+    SkPoint pts[] = {{5, 4}, {6, 5}, {7, 6}, {6, 6}, {4, 6}, {5, 7}, {5, 5}, {5, 4}, {6, 5}, {7, 6}};
+    for (int i = 0; i < 3; ++i) {
+        p.reset();
+        p.setFillType(SkPath::kEvenOdd_FillType);
+        p.moveTo(pts[i].fX, pts[i].fY);
+        p.cubicTo(pts[i + 1].fX, pts[i + 1].fY, pts[i + 2].fX, pts[i + 2].fY, pts[i + 3].fX, pts[i + 3].fY);
+        p.cubicTo(pts[i + 4].fX, pts[i + 4].fY, pts[i + 5].fX, pts[i + 5].fY, pts[i + 6].fX, pts[i + 6].fY);
+        p.close();
+        REPORTER_ASSERT(reporter, p.contains(5.5f, 5.5f));
+        REPORTER_ASSERT(reporter, !p.contains(4.5f, 5.5f));
+    }
+}
+
+static void test_operatorEqual(skiatest::Reporter* reporter) {
+    SkPath a;
+    SkPath b;
+    REPORTER_ASSERT(reporter, a == a);
+    REPORTER_ASSERT(reporter, a == b);
+    a.setFillType(SkPath::kInverseWinding_FillType);
+    REPORTER_ASSERT(reporter, a != b);
+    a.reset();
+    REPORTER_ASSERT(reporter, a == b);
+    a.lineTo(1, 1);
+    REPORTER_ASSERT(reporter, a != b);
+    a.reset();
+    REPORTER_ASSERT(reporter, a == b);
+    a.lineTo(1, 1);
+    b.lineTo(1, 2);
+    REPORTER_ASSERT(reporter, a != b);
+    a.reset();
+    a.lineTo(1, 2);
+    REPORTER_ASSERT(reporter, a == b);
+}
+
+class PathTest_Private {
+public:
+    static void TestPathTo(skiatest::Reporter* reporter) {
+        SkPath p, q;
+        p.lineTo(4, 4);
+        p.reversePathTo(q);
+        check_path_is_line(reporter, &p, 4, 4);
+        q.moveTo(-4, -4);
+        p.reversePathTo(q);
+        check_path_is_line(reporter, &p, 4, 4);
+        q.lineTo(7, 8);
+        q.conicTo(8, 7, 6, 5, 0.5f);
+        q.quadTo(6, 7, 8, 6);
+        q.cubicTo(5, 6, 7, 8, 7, 5);
+        q.close();
+        p.reversePathTo(q);
+        SkRect reverseExpected = {-4, -4, 8, 8};
+        REPORTER_ASSERT(reporter, p.getBounds() == reverseExpected);
+    }
+};
+
 static void TestPath(skiatest::Reporter* reporter) {
     SkTSize<SkScalar>::Make(3,4);
 
@@ -2609,6 +3145,13 @@
 
     REPORTER_ASSERT(reporter, p.getBounds().isEmpty());
 
+    // this triggers a code path in SkPath::operator= which is otherwise unexercised
+    SkPath& self = p;
+    p = self;
+
+    // this triggers a code path in SkPath::swap which is otherwise unexercised
+    p.swap(self);
+
     bounds.set(0, 0, SK_Scalar1, SK_Scalar1);
 
     p.addRoundRect(bounds, SK_Scalar1, SK_Scalar1);
@@ -2668,6 +3211,7 @@
     p.addRect(bounds);
     REPORTER_ASSERT(reporter, !p.isRect(NULL));
 
+    test_operatorEqual(reporter);
     test_isLine(reporter);
     test_isRect(reporter);
     test_isNestedRects(reporter);
@@ -2691,6 +3235,7 @@
     test_isfinite_after_transform(reporter);
     test_arb_round_rect_is_convex(reporter);
     test_arb_zero_rad_round_rect_is_rect(reporter);
+    test_addrect(reporter);
     test_addrect_isfinite(reporter);
     test_tricky_cubic();
     test_clipped_cubic();
@@ -2702,6 +3247,13 @@
     test_path_close_issue1474(reporter);
     test_path_to_region(reporter);
     test_rrect(reporter);
+    test_arc(reporter);
+    test_arcTo(reporter);
+    test_addPath(reporter);
+    test_conicTo_special_case(reporter);
+    test_get_point(reporter);
+    test_contains(reporter);
+    PathTest_Private::TestPathTo(reporter);
 }
 
 #include "TestClassDef.h"
diff --git a/tools/coverage.sh b/tools/coverage.sh
index 3e34806..8fe75c5 100755
--- a/tools/coverage.sh
+++ b/tools/coverage.sh
@@ -27,5 +27,5 @@
 
 lcov $QUIET -a /tmp/baseline -a /tmp/coverage -o /tmp/merged
 
-genhtml $QUIET /tmp/merged -o out/Coverage/report
+genhtml $QUIET /tmp/merged --legend -o out/Coverage/report
 xdg-open out/Coverage/report/index.html