// This is free and unencumbered software released into the public domain.

// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.

// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

// For more information, please refer to <http://unlicense.org>

#include <algorithm>
#include <complex>
#include <vector>

using namespace std;

#define X real()
#define Y imag()

typedef long double Real;
typedef complex<Real> Point;
struct Line {
  Point a, b;
  bool operator<(const Line &line) const;
};
typedef vector<Point> Polygon;

constexpr Real PI = acos(Real(-1));

// 比較関数
int sgn(const Real &a, const Real &b = 0) {
  constexpr Real EPS = 1e-8;
  return a < b - EPS ? -1 : a > b + EPS ? 1 : 0;
}

bool near(const Point &a, const Point &b) { return !sgn(norm(a - b)); }
namespace std {
bool operator<(const Point &a, const Point &b) {
  return sgn(a.X, b.X) ? a.X < b.X : a.Y < b.Y;
}
} // namespace std

bool Line::operator<(const Line &line) const {
  return !near(a, line.a) ? a < line.a : b < line.b;
}

// 平方根
Real sr(const Real &a) { return sqrt(max<Real>(a, 0)); }

// 内積
Real dot(const Point &a, const Point &b) { return a.X * b.X + a.Y * b.Y; }
// 外積
Real det(const Point &a, const Point &b) { return a.X * b.Y - a.Y * b.X; }

// 線分のベクトル
Point vec(const Line &a) { return a.b - a.a; }

// 線分abに対する点cの位置
enum CCW { FRONT = 1, RIGHT = 2, BACK = 4, LEFT = 8, ON = 16 };
int ccw(const Point &a, const Point &b, const Point &c) {
  if (near(a, c) || near(b, c)) {
    return ON;
  }
  int s = sgn(det(b - a, c - a));
  if (s) {
    return s > 0 ? LEFT : RIGHT;
  }
  return (a < b) == (b < c) ? FRONT : (c < a) == (a < b) ? BACK : ON;
}
int ccw(const Line &l, const Point &p) { return ccw(l.a, l.b, p); }

// 有向角度
Real arg(const Point &base, const Point &a, const Point &b) {
  return arg((b - base) / (a - base));
}

Line sortBase;
bool lessArg(const Point &a, const Point &b) {
  Real ang1 = arg(sortBase.a, sortBase.b, a);
  Real ang2 = arg(sortBase.a, sortBase.b, b);
  return sgn(ang1, ang2) ? ang1 < ang2 : norm(a) > norm(b);
}

// 射影
Point proj(const Line &l, const Point &p) {
  auto v = vec(l);
  return l.a + ((p - l.a) / v).X * v;
}
Point refl(const Line &l, const Point &p) {
  auto v = vec(l);
  return l.a + conj((p - l.a) / v) * v;
}

// 交差判定
bool eqL(const Line &a, const Line &b) {
  return !sgn(det(vec(a), vec(b))) && !sgn(det(vec(a), b.a - a.a));
}
bool iLL(const Line &a, const Line &b) {
  return sgn(det(vec(a), vec(b))) || !sgn(det(vec(a), b.a - a.a));
}
bool iLLs(const Line &a, const Line &b) { return sgn(det(vec(a), vec(b))); }
bool iLS(const Line &a, const Line &b) {
  return sgn(det(vec(a), b.a - a.a)) * sgn(det(vec(a), b.b - a.a)) <= 0;
}
bool iLSs(const Line &a, const Line &b) {
  return sgn(det(vec(a), b.a - a.a)) * sgn(det(vec(a), b.b - a.a)) < 0;
}
bool iSS(const Line &a, const Line &b) {
  int cwa = ccw(a.a, a.b, b.a) | ccw(a.a, a.b, b.b);
  int cwb = ccw(b.a, b.b, a.a) | ccw(b.a, b.b, a.b);
  return ((cwa | cwb) & ON) || ((cwa & cwb) == (LEFT | RIGHT));
}
bool iSSs(const Line &a, const Line &b) {
  int cwa = ccw(a.a, a.b, b.a) | ccw(a.a, a.b, b.b);
  int cwb = ccw(b.a, b.b, a.a) | ccw(b.a, b.b, a.b);
  return (cwa & cwb) == (LEFT | RIGHT);
}

// 交点
Point pLL(const Line &a, const Line &b) {
  return a.a + vec(a) * (det(vec(b), b.a - a.a) / det(vec(b), vec(a)));
}

// 距離
Real dLP(const Line &l, const Point &p) {
  return abs(det(vec(l), p - l.a) / vec(l));
}
Real dSP(const Line &s, const Point &p) {
  if (dot(vec(s), p - s.a) < 0) {
    return abs(p - s.a);
  }
  if (dot(vec(s), p - s.b) > 0) {
    return abs(p - s.b);
  }
  return dLP(s, p);
}
Real dLL(const Line &a, const Line &b) {
  if (iLL(a, b)) {
    return 0;
  }
  return dLP(a, b.a);
}
Real dLS(const Line &a, const Line &b) {
  if (iLS(a, b)) {
    return 0;
  }
  return min(dLP(a, b.a), dLP(a, b.b));
}
Real dSS(const Line &a, const Line &b) {
  if (iSS(a, b)) {
    return 0;
  }
  return min({dSP(a, b.a), dSP(a, b.b), dSP(b, a.a), dSP(b, a.b)});
}

// 円
struct Circle {
  Point c;
  Real r;
};

// 交差判定
bool iCP(const Circle &c, const Point &p) {
  return sgn(abs(p - c.c), c.r) <= 0;
}
bool iCL(const Circle &c, const Line &l) { return sgn(c.r, dLP(l, c.c)) >= 0; }
bool iCS(const Circle &c, const Line &s) { return sgn(c.r, dSP(s, c.c)) >= 0; }
bool iCSc(const Circle &c, const Line &s) {
  return iCS(c, s) && sgn(c.r, max(abs(s.a - c.c), abs(s.b - c.c))) >= 0;
}
bool iCC(const Circle &a, const Circle &b) {
  return sgn(abs(a.c - b.c), a.r + b.r) <= 0;
}
bool iCCc(const Circle &a, const Circle &b) {
  return iCC(a, b) && sgn(abs(a.c - b.c), abs(a.r - b.r)) >= 0;
}

// 交点
pair<Point, Point> pCC(const Circle &a, const Circle &b) {
  Real x = (norm(a.c - b.c) + a.r * a.r - b.r * b.r) / (2 * abs(a.c - b.c));
  Point e = (b.c - a.c) / abs(b.c - a.c);
  Point y = e * Point(0, sr(a.r * a.r - x * x));
  return make_pair(a.c + e * x - y, a.c + e * x + y);
}
pair<Point, Point> pCL(const Circle &c, const Line &l) {
  Point x = proj(l, c.c);
  Point y = vec(l) / abs(vec(l)) * sr(c.r * c.r - norm(x - c.c));
  return make_pair(x - y, x + y);
}

// 角の内外判定 角abの内部にあれば正、辺上は0、外部は負
int sAP(const Point &a, const Point &b, const Point &c) {
  return sgn(det(a, c)) - sgn(det(b, c)) - sgn(det(a, b));
}

// 多角形の面積
Real aPol(const Polygon &vp) {
  Real res = 0;
  for (int i = 0; i < int(vp.size()); ++i) {
    res += det(vp[i], vp[(i + 1) % vp.size()]);
  }
  return res / 2;
}

// 多角形の内外判定 内部:1、周上:0、外部:-1
int sGP(const Polygon &pol, const Point &p) {
  int side = -1;
  for (int i = 0; i < int(pol.size()); ++i) {
    Point p0 = pol[i] - p, p1 = pol[(i + 1) % pol.size()] - p;
    if (ccw(p0, p1, 0) == ON) {
      return 0;
    }
    if (p0.Y > p1.Y) {
      swap(p0, p1);
    }
    if (sgn(p0.Y) <= 0 && 0 < sgn(p1.Y) && sgn(det(p0, p1)) > 0) {
      side *= -1;
    }
  }
  return side;
}

// 凸包
Polygon convexHull(vector<Point> p) {
  int m = -1, n = p.size();
  if (n < 3) {
    return p;
  }
  vector<Point> q(n * 2);
  sort(p.begin(), p.end());
  for (int i = 0; i < n; q[++m] = p[i++]) {
    for (; m > 0 && ccw(q[m - 1], q[m], p[i]) != LEFT; --m) {
      ;
    }
  }
  for (int i = n - 2, r = m; i >= 0; q[++m] = p[i--]) {
    for (; m > r && ccw(q[m - 1], q[m], p[i]) != LEFT; --m) {
      ;
    }
  }
  q.resize(m);
  return q;
}

// 凸多角形切断
Polygon convexCut(const Polygon &p, const Line &l) {
  vector<Point> q;
  for (int i = 0; i < int(p.size()); ++i) {
    if (ccw(l.a, l.b, p[i]) != RIGHT) {
      q.push_back(p[i]);
    }
    Line s = {p[i], p[(i + 1) % p.size()]};
    if (iLSs(l, s)) {
      q.push_back(pLL(l, s));
    }
  }
  return q;
}

// 線分をマージする
vector<Line> merge(vector<Line> s) {
  for (int i = 0; i < int(s.size()); ++i) {
    if (s[i].b < s[i].a) {
      swap(s[i].a, s[i].b);
    }
  }
  sort(s.begin(), s.end());
  for (int i = 0; i < int(s.size()); ++i) {
    for (int j = 0; j < i; ++j) {
      if (iSS(s[i], s[j]) && !iLLs(s[i], s[j])) {
        s[j].b = max(s[i].b, s[j].b);
        s.erase(s.begin() + i--);
        break;
      }
    }
  }
  return s;
}

// 線分アレンジメント 隣の点への辺のみを持つ
vector<vector<int>> sArr(vector<Line> s, vector<Point> &vp) {
  s = merge(s);
  for (int i = 0; i < int(s.size()); ++i) {
    vp.push_back(s[i].a);
    vp.push_back(s[i].b);
  }
  for (int i = 0; i < int(s.size()); ++i) {
    for (int j = 0; j < i; ++j) {
      if (iSS(s[i], s[j])) {
        vp.push_back(pLL(s[i], s[j]));
      }
    }
  }
  sort(vp.begin(), vp.end());
  vp.erase(unique(vp.begin(), vp.end(), near), vp.end());
  vector<vector<int>> graph(vp.size());
  for (int i = 0; i < int(s.size()); ++i) {
    vector<pair<Real, int>> v;
    for (int j = 0; j < int(vp.size()); ++j) {
      if (ccw(s[i].a, s[i].b, vp[j]) == ON) {
        v.push_back(make_pair(norm(vp[j] - s[i].a), j));
      }
    }
    sort(v.begin(), v.end());
    for (int j = 0; j < int(v.size()) - 1; ++j) {
      graph[v[j + 1].second].push_back(v[j].second);
      graph[v[j].second].push_back(v[j + 1].second);
    }
  }
  return graph;
}

#include <iomanip>
#include <iostream>

int main() {
  int q;
  cin >> q;
  for (int i = 0; i < q; ++i) {
    Real p1x, p1y, p2x, p2y;
    cin >> p1x >> p1y >> p2x >> p2y;
    Line line1{Point(p1x, p1y), Point(p2x, p2y)};
    Real p3x, p3y, p4x, p4y;
    cin >> p3x >> p3y >> p4x >> p4y;
    Line line2{Point(p3x, p3y), Point(p4x, p4y)};
    if (!iLLs(line1, line2)) {
      cout << 2 << endl;
    } else if (sgn(dot(vec(line1), vec(line2))) == 0) {
      cout << 1 << endl;
    } else {
      cout << 0 << endl;
    }
  }
}



Measured memory_bytes: 3248