Refactor system_test

1. Move common test infrastructure into test_util.
2. system_test now only contains powells function.
3. Add bundle_adjustment_test.

Instead of a single function which computes everything,
there is now a test for each solver configuration which
uses the reference solution computed by the fixture.

Change-Id: I16a9a9a83a845a7aaf28762bcecf1a8ff5aee805
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 55643de..6c71c29 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -285,6 +285,7 @@
   ceres_test(block_random_access_diagonal_matrix)
   ceres_test(block_random_access_sparse_matrix)
   ceres_test(block_sparse_matrix)
+  ceres_test(bundle_adjustment)
   ceres_test(c_api)
   ceres_test(canonical_views_clustering)
   ceres_test(compressed_row_sparse_matrix)
diff --git a/internal/ceres/bundle_adjustment_test.cc b/internal/ceres/bundle_adjustment_test.cc
new file mode 100644
index 0000000..c2f8843
--- /dev/null
+++ b/internal/ceres/bundle_adjustment_test.cc
@@ -0,0 +1,560 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2015 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+//   this list of conditions and the following disclaimer in the documentation
+//   and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+//   used to endorse or promote products derived from this software without
+//   specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: keir@google.com (Keir Mierle)
+//         sameeragarwal@google.com (Sameer Agarwal)
+//
+// End-to-end bundle adjustment tests for Ceres. It uses a bundle
+// adjustment problem with 16 cameras and two thousand points.
+
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include "ceres/internal/port.h"
+
+#include "ceres/autodiff_cost_function.h"
+#include "ceres/ordered_groups.h"
+#include "ceres/problem.h"
+#include "ceres/rotation.h"
+#include "ceres/solver.h"
+#include "ceres/stringprintf.h"
+#include "ceres/test_util.h"
+#include "ceres/types.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+#include "gtest/gtest.h"
+
+namespace ceres {
+namespace internal {
+
+using std::string;
+using std::vector;
+
+const bool kAutomaticOrdering = true;
+const bool kUserOrdering = false;
+
+// This class implements the SystemTestProblem interface and provides
+// access to a bundle adjustment problem. It is based on
+// examples/bundle_adjustment_example.cc. Currently a small 16 camera
+// problem is hard coded in the constructor.
+class BundleAdjustmentProblem {
+ public:
+  BundleAdjustmentProblem() {
+    const string input_file = TestFileAbsolutePath("problem-16-22106-pre.txt");
+    ReadData(input_file);
+    BuildProblem();
+  }
+
+  ~BundleAdjustmentProblem() {
+    delete []point_index_;
+    delete []camera_index_;
+    delete []observations_;
+    delete []parameters_;
+  }
+
+  Problem* mutable_problem() { return &problem_; }
+  Solver::Options* mutable_solver_options() { return &options_; }
+
+  int num_cameras()            const { return num_cameras_;        }
+  int num_points()             const { return num_points_;         }
+  int num_observations()       const { return num_observations_;   }
+  const int* point_index()     const { return point_index_;  }
+  const int* camera_index()    const { return camera_index_; }
+  const double* observations() const { return observations_; }
+  double* mutable_cameras() { return parameters_; }
+  double* mutable_points() { return parameters_  + 9 * num_cameras_; }
+
+  static double kResidualTolerance;
+
+ private:
+  void ReadData(const string& filename) {
+    FILE * fptr = fopen(filename.c_str(), "r");
+
+    if (!fptr) {
+      LOG(FATAL) << "File Error: unable to open file " << filename;
+    }
+
+    // This will die horribly on invalid files. Them's the breaks.
+    FscanfOrDie(fptr, "%d", &num_cameras_);
+    FscanfOrDie(fptr, "%d", &num_points_);
+    FscanfOrDie(fptr, "%d", &num_observations_);
+
+    VLOG(1) << "Header: " << num_cameras_
+            << " " << num_points_
+            << " " << num_observations_;
+
+    point_index_ = new int[num_observations_];
+    camera_index_ = new int[num_observations_];
+    observations_ = new double[2 * num_observations_];
+
+    num_parameters_ = 9 * num_cameras_ + 3 * num_points_;
+    parameters_ = new double[num_parameters_];
+
+    for (int i = 0; i < num_observations_; ++i) {
+      FscanfOrDie(fptr, "%d", camera_index_ + i);
+      FscanfOrDie(fptr, "%d", point_index_ + i);
+      for (int j = 0; j < 2; ++j) {
+        FscanfOrDie(fptr, "%lf", observations_ + 2*i + j);
+      }
+    }
+
+    for (int i = 0; i < num_parameters_; ++i) {
+      FscanfOrDie(fptr, "%lf", parameters_ + i);
+    }
+  }
+
+  void BuildProblem() {
+    double* points = mutable_points();
+    double* cameras = mutable_cameras();
+
+    for (int i = 0; i < num_observations(); ++i) {
+      // Each Residual block takes a point and a camera as input and
+      // outputs a 2 dimensional residual.
+      CostFunction* cost_function =
+          new AutoDiffCostFunction<BundlerResidual, 2, 9, 3>(
+              new BundlerResidual(observations_[2*i + 0],
+                                  observations_[2*i + 1]));
+
+      // Each observation correponds to a pair of a camera and a point
+      // which are identified by camera_index()[i] and
+      // point_index()[i] respectively.
+      double* camera = cameras + 9 * camera_index_[i];
+      double* point = points + 3 * point_index()[i];
+      problem_.AddResidualBlock(cost_function, NULL, camera, point);
+    }
+
+    options_.linear_solver_ordering.reset(new ParameterBlockOrdering);
+
+    // The points come before the cameras.
+    for (int i = 0; i < num_points_; ++i) {
+      options_.linear_solver_ordering->AddElementToGroup(points + 3 * i, 0);
+    }
+
+    for (int i = 0; i < num_cameras_; ++i) {
+      options_.linear_solver_ordering->AddElementToGroup(cameras + 9 * i, 1);
+    }
+
+    options_.linear_solver_type = DENSE_SCHUR;
+    options_.max_num_iterations = 25;
+    options_.function_tolerance = 1e-10;
+    options_.gradient_tolerance = 1e-10;
+    options_.parameter_tolerance = 1e-10;
+  }
+
+  template<typename T>
+  void FscanfOrDie(FILE *fptr, const char *format, T *value) {
+    int num_scanned = fscanf(fptr, format, value);
+    if (num_scanned != 1) {
+      LOG(FATAL) << "Invalid UW data file.";
+    }
+  }
+
+  // Templated pinhole camera model.  The camera is parameterized
+  // using 9 parameters. 3 for rotation, 3 for translation, 1 for
+  // focal length and 2 for radial distortion. The principal point is
+  // not modeled (i.e. it is assumed be located at the image center).
+  struct BundlerResidual {
+    // (u, v): the position of the observation with respect to the image
+    // center point.
+    BundlerResidual(double u, double v): u(u), v(v) {}
+
+    template <typename T>
+    bool operator()(const T* const camera,
+                    const T* const point,
+                    T* residuals) const {
+      T p[3];
+      AngleAxisRotatePoint(camera, point, p);
+
+      // Add the translation vector
+      p[0] += camera[3];
+      p[1] += camera[4];
+      p[2] += camera[5];
+
+      const T& focal = camera[6];
+      const T& l1 = camera[7];
+      const T& l2 = camera[8];
+
+      // Compute the center of distortion.  The sign change comes from
+      // the camera model that Noah Snavely's Bundler assumes, whereby
+      // the camera coordinate system has a negative z axis.
+      T xp = - focal * p[0] / p[2];
+      T yp = - focal * p[1] / p[2];
+
+      // Apply second and fourth order radial distortion.
+      T r2 = xp*xp + yp*yp;
+      T distortion = T(1.0) + r2  * (l1 + l2  * r2);
+
+      residuals[0] = distortion * xp - T(u);
+      residuals[1] = distortion * yp - T(v);
+
+      return true;
+    }
+
+    double u;
+    double v;
+  };
+
+  Problem problem_;
+  Solver::Options options_;
+
+  int num_cameras_;
+  int num_points_;
+  int num_observations_;
+  int num_parameters_;
+
+  int* point_index_;
+  int* camera_index_;
+  double* observations_;
+  // The parameter vector is laid out as follows
+  // [camera_1, ..., camera_n, point_1, ..., point_m]
+  double* parameters_;
+};
+
+double BundleAdjustmentProblem::kResidualTolerance = 1e-4;
+typedef SystemTest<BundleAdjustmentProblem> BundleAdjustmentTest;
+
+TEST_F(BundleAdjustmentTest, DenseSchurWithAutomaticOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(DENSE_SCHUR, NO_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest, DenseSchurWithUserOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(DENSE_SCHUR, NO_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest, IterativeSchurWithJacobiAndAutomaticOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR, NO_SPARSE, kAutomaticOrdering, JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest, IterativeSchurWithJacobiAndUserOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR, NO_SPARSE, kUserOrdering, JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchurWithSchurJacobiAndAutomaticOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR,
+                   NO_SPARSE,
+                   kAutomaticOrdering,
+                   SCHUR_JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest, IterativeSchurWithSchurJacobiAndUserOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR, NO_SPARSE, kUserOrdering, SCHUR_JACOBI));
+}
+
+#ifndef CERES_NO_SUITESPARSE
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholeskyWithAutomaticOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholeskyWithUserOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       SparseSchurWithAutomaticOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_SCHUR, SUITE_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest, SparseSchurWithUserOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_SCHUR, SUITE_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchurWithClusterJacobiAndAutomaticOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR,
+                   SUITE_SPARSE,
+                   kAutomaticOrdering,
+                   CLUSTER_JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchurWithClusterJacobiAndUserOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR,
+                   SUITE_SPARSE,
+                   kUserOrdering,
+                   CLUSTER_JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchurWithClusterTridiagonalAndAutomaticOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR,
+                   SUITE_SPARSE,
+                   kAutomaticOrdering,
+                   CLUSTER_TRIDIAGONAL));
+}
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchurWithClusterTridiagonalAndUserOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR,
+                   SUITE_SPARSE,
+                   kUserOrdering,
+                   CLUSTER_TRIDIAGONAL));
+}
+#endif  // CERES_NO_SUITESPARSE
+
+#ifndef CERES_NO_CXSPARSE
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholeskyWithAutomaticOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholeskyWithUserOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest, SparseSchurWithAutomaticOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_SCHUR, CX_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest, SparseSchurWithUserOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_SCHUR, CX_SPARSE, kUserOrdering));
+}
+#endif  // CERES_NO_CXSPARSE
+
+#ifdef CERES_USE_EIGEN_SPARSE
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholeskyWithAutomaticOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, EIGEN_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholeskyWithUserOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, EIGEN_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       SparseSchurWithAutomaticOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_SCHUR, EIGEN_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest, SparseSchurWithUserOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_SCHUR, EIGEN_SPARSE, kUserOrdering));
+}
+#endif  // CERES_USE_EIGEN_SPARSE
+
+#ifdef CERES_USE_OPENMP
+
+TEST_F(BundleAdjustmentTest, MultiThreadedDenseSchurWithAutomaticOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(DENSE_SCHUR, NO_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest, MultiThreadedDenseSchurWithUserOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(DENSE_SCHUR, NO_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithJacobiAndAutomaticOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR,
+                           NO_SPARSE,
+                           kAutomaticOrdering,
+                           JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithJacobiAndUserOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR, NO_SPARSE, kUserOrdering, JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithSchurJacobiAndAutomaticOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR,
+                           NO_SPARSE,
+                           kAutomaticOrdering,
+                           SCHUR_JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithSchurJacobiAndUserOrdering) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR,
+                           NO_SPARSE,
+                           kUserOrdering,
+                           SCHUR_JACOBI));
+}
+
+#ifndef CERES_NO_SUITESPARSE
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseNormalCholeskyWithAutomaticOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_NORMAL_CHOLESKY,
+                           SUITE_SPARSE,
+                           kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseNormalCholeskyWithUserOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_NORMAL_CHOLESKY,
+                           SUITE_SPARSE,
+                           kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseSchurWithAutomaticOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_SCHUR,
+                           SUITE_SPARSE,
+                           kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseSchurWithUserOrderingUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_SCHUR, SUITE_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithClusterJacobiAndAutomaticOrderingUsingSuiteSparse) {  // NOLINT
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR,
+                           SUITE_SPARSE,
+                           kAutomaticOrdering,
+                           CLUSTER_JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithClusterJacobiAndUserOrderingUsingSuiteSparse) {  // NOLINT
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR,
+                           SUITE_SPARSE,
+                           kUserOrdering,
+                           CLUSTER_JACOBI));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithClusterTridiagonalAndAutomaticOrderingUsingSuiteSparse) {  // NOLINT
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR,
+                           SUITE_SPARSE,
+                           kAutomaticOrdering,
+                           CLUSTER_TRIDIAGONAL));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedIterativeSchurWithClusterTridiagonalAndUserOrderingUsingSuiteSparse) {  // NOTLINT
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(ITERATIVE_SCHUR,
+                           SUITE_SPARSE,
+                           kUserOrdering,
+                           CLUSTER_TRIDIAGONAL));
+}
+#endif  // CERES_NO_SUITESPARSE
+
+#ifndef CERES_NO_CXSPARSE
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseNormalCholeskyWithAutomaticOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_NORMAL_CHOLESKY,
+                           CX_SPARSE,
+                           kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseNormalCholeskyWithUserOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseSchurWithAutomaticOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_SCHUR, CX_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseSchurWithUserOrderingUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_SCHUR, CX_SPARSE, kUserOrdering));
+}
+#endif  // CERES_NO_CXSPARSE
+
+#ifdef CERES_USE_EIGEN_SPARSE
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseNormalCholeskyWithAutomaticOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_NORMAL_CHOLESKY,
+                           EIGEN_SPARSE,
+                           kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseNormalCholeskyWithUserOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_NORMAL_CHOLESKY,
+                           EIGEN_SPARSE,
+                           kUserOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseSchurWithAutomaticOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_SCHUR, EIGEN_SPARSE, kAutomaticOrdering));
+}
+
+TEST_F(BundleAdjustmentTest,
+       MultiThreadedSparseSchurWithUserOrderingUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      ThreadedSolverConfig(SPARSE_SCHUR, EIGEN_SPARSE, kUserOrdering));
+}
+#endif  // CERES_USE_EIGEN_SPARSE
+#endif  // CERES_USE_OPENMP
+
+}  // namespace internal
+}  // namespace ceres
diff --git a/internal/ceres/system_test.cc b/internal/ceres/system_test.cc
index 51812cc..5fb608e 100644
--- a/internal/ceres/system_test.cc
+++ b/internal/ceres/system_test.cc
@@ -29,173 +29,22 @@
 // Author: keir@google.com (Keir Mierle)
 //         sameeragarwal@google.com (Sameer Agarwal)
 //
-// System level tests for Ceres. The current suite of two tests. The
-// first test is a small test based on Powell's Function. It is a
-// scalar problem with 4 variables. The second problem is a bundle
-// adjustment problem with 16 cameras and two thousand cameras. The
-// first problem is to test the sanity test the factorization based
-// solvers. The second problem is used to test the various
-// combinations of solvers, orderings, preconditioners and
-// multithreading.
+// End-to-end tests for Ceres using Powell's function.
 
 #include <cmath>
-#include <cstdio>
 #include <cstdlib>
-#include <string>
-
-#include "ceres/internal/port.h"
 
 #include "ceres/autodiff_cost_function.h"
-#include "ceres/ordered_groups.h"
 #include "ceres/problem.h"
-#include "ceres/rotation.h"
 #include "ceres/solver.h"
-#include "ceres/stringprintf.h"
 #include "ceres/test_util.h"
 #include "ceres/types.h"
-#include "gflags/gflags.h"
 #include "glog/logging.h"
 #include "gtest/gtest.h"
 
 namespace ceres {
 namespace internal {
 
-using std::string;
-using std::vector;
-
-const bool kAutomaticOrdering = true;
-const bool kUserOrdering = false;
-
-// Struct used for configuring the solver.
-struct SolverConfig {
-  SolverConfig(
-      LinearSolverType linear_solver_type,
-      SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
-      bool use_automatic_ordering)
-      : linear_solver_type(linear_solver_type),
-        sparse_linear_algebra_library_type(sparse_linear_algebra_library_type),
-        use_automatic_ordering(use_automatic_ordering),
-        preconditioner_type(IDENTITY),
-        num_threads(1) {
-  }
-
-  SolverConfig(
-      LinearSolverType linear_solver_type,
-      SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
-      bool use_automatic_ordering,
-      PreconditionerType preconditioner_type)
-      : linear_solver_type(linear_solver_type),
-        sparse_linear_algebra_library_type(sparse_linear_algebra_library_type),
-        use_automatic_ordering(use_automatic_ordering),
-        preconditioner_type(preconditioner_type),
-        num_threads(1) {
-  }
-
-  string ToString() const {
-    return StringPrintf(
-        "(%s, %s, %s, %s, %d)",
-        LinearSolverTypeToString(linear_solver_type),
-        SparseLinearAlgebraLibraryTypeToString(
-            sparse_linear_algebra_library_type),
-        use_automatic_ordering ? "AUTOMATIC" : "USER",
-        PreconditionerTypeToString(preconditioner_type),
-        num_threads);
-  }
-
-  LinearSolverType linear_solver_type;
-  SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type;
-  bool use_automatic_ordering;
-  PreconditionerType preconditioner_type;
-  int num_threads;
-};
-
-// Templated function that given a set of solver configurations,
-// instantiates a new copy of SystemTestProblem for each configuration
-// and solves it. The solutions are expected to have residuals with
-// coordinate-wise maximum absolute difference less than or equal to
-// max_abs_difference.
-//
-// The template parameter SystemTestProblem is expected to implement
-// the following interface.
-//
-//   class SystemTestProblem {
-//     public:
-//       SystemTestProblem();
-//       Problem* mutable_problem();
-//       Solver::Options* mutable_solver_options();
-//   };
-template <typename SystemTestProblem>
-void RunSolversAndCheckTheyMatch(
-    const vector<SolverConfig>& configurations,
-    const double max_abs_difference) {
-  int num_configurations = configurations.size();
-  vector<SystemTestProblem*> problems;
-  vector<vector<double> > final_residuals(num_configurations);
-
-  for (int i = 0; i < num_configurations; ++i) {
-    SystemTestProblem* system_test_problem = new SystemTestProblem();
-
-    const SolverConfig& config = configurations[i];
-
-    Solver::Options& options = *(system_test_problem->mutable_solver_options());
-    options.linear_solver_type = config.linear_solver_type;
-    options.sparse_linear_algebra_library_type =
-        config.sparse_linear_algebra_library_type;
-    options.preconditioner_type = config.preconditioner_type;
-    options.num_threads = config.num_threads;
-    options.num_linear_solver_threads = config.num_threads;
-
-    if (config.use_automatic_ordering) {
-      options.linear_solver_ordering.reset();
-    }
-
-    LOG(INFO) << "Running solver configuration: "
-              << config.ToString();
-
-    Solver::Summary summary;
-    Solve(options,
-          system_test_problem->mutable_problem(),
-          &summary);
-
-    system_test_problem
-        ->mutable_problem()
-        ->Evaluate(Problem::EvaluateOptions(),
-                   NULL,
-                   &final_residuals[i],
-                   NULL,
-                   NULL);
-
-    CHECK_NE(summary.termination_type, ceres::FAILURE)
-        << "Solver configuration " << i << " failed.";
-    problems.push_back(system_test_problem);
-
-    // Compare the resulting solutions to each other. Arbitrarily take
-    // SPARSE_NORMAL_CHOLESKY as the golden solve. We compare
-    // solutions by comparing their residual vectors. We do not
-    // compare parameter vectors because it is much more brittle and
-    // error prone to do so, since the same problem can have nearly
-    // the same residuals at two completely different positions in
-    // parameter space.
-    if (i > 0) {
-      const vector<double>& reference_residuals = final_residuals[0];
-      const vector<double>& current_residuals = final_residuals[i];
-
-      for (int j = 0; j < reference_residuals.size(); ++j) {
-        EXPECT_NEAR(current_residuals[j],
-                    reference_residuals[j],
-                    max_abs_difference)
-            << "Not close enough residual:" << j
-            << " reference " << reference_residuals[j]
-            << " current " << current_residuals[j];
-      }
-    }
-  }
-
-  for (int i = 0; i < num_configurations; ++i) {
-    delete problems[i];
-  }
-}
-
 // This class implements the SystemTestProblem interface and provides
 // access to an implementation of Powell's singular function.
 //
@@ -229,12 +78,17 @@
     problem_.AddResidualBlock(
         new AutoDiffCostFunction<F4, 1, 1, 1>(new F4), NULL, &x_[0], &x_[3]);
 
+    // Settings for the reference solution.
+    options_.linear_solver_type = ceres::DENSE_QR;
     options_.max_num_iterations = 10;
+    options_.num_threads = 1;
   }
 
   Problem* mutable_problem() { return &problem_; }
   Solver::Options* mutable_solver_options() { return &options_; }
 
+  static double kResidualTolerance;
+
  private:
   // Templated functions used for automatically differentiated cost
   // functions.
@@ -287,274 +141,51 @@
   Solver::Options options_;
 };
 
-TEST(SystemTest, PowellsFunction) {
-  vector<SolverConfig> configs;
-#define CONFIGURE(linear_solver, sparse_linear_algebra_library_type, ordering) \
-  configs.push_back(SolverConfig(linear_solver,                         \
-                                 sparse_linear_algebra_library_type,    \
-                                 ordering))
+double PowellsFunction::kResidualTolerance = 1e-8;
 
-  CONFIGURE(DENSE_QR,               SUITE_SPARSE, kAutomaticOrdering);
-  CONFIGURE(DENSE_NORMAL_CHOLESKY,  SUITE_SPARSE, kAutomaticOrdering);
-  CONFIGURE(DENSE_SCHUR,            SUITE_SPARSE, kAutomaticOrdering);
+typedef SystemTest<PowellsFunction> PowellTest;
+const bool kAutomaticOrdering = true;
 
-#ifndef CERES_NO_SUITESPARSE
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, kAutomaticOrdering);
-#endif  // CERES_NO_SUITESPARSE
-
-#ifndef CERES_NO_CXSPARSE
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE,    kAutomaticOrdering);
-#endif  // CERES_NO_CXSPARSE
-
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kAutomaticOrdering);
-
-#undef CONFIGURE
-
-  const double kMaxAbsoluteDifference = 1e-8;
-  RunSolversAndCheckTheyMatch<PowellsFunction>(configs, kMaxAbsoluteDifference);
+TEST_F(PowellTest, DenseQR) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(DENSE_QR, NO_SPARSE));
 }
 
-// This class implements the SystemTestProblem interface and provides
-// access to a bundle adjustment problem. It is based on
-// examples/bundle_adjustment_example.cc. Currently a small 16 camera
-// problem is hard coded in the constructor. Going forward we may
-// extend this to a larger number of problems.
-class BundleAdjustmentProblem {
- public:
-  BundleAdjustmentProblem() {
-    const string input_file = TestFileAbsolutePath("problem-16-22106-pre.txt");
-    ReadData(input_file);
-    BuildProblem();
-  }
+TEST_F(PowellTest, DenseNormalCholesky) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(DENSE_NORMAL_CHOLESKY));
+}
 
-  ~BundleAdjustmentProblem() {
-    delete []point_index_;
-    delete []camera_index_;
-    delete []observations_;
-    delete []parameters_;
-  }
+TEST_F(PowellTest, DenseSchur) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(DENSE_SCHUR));
+}
 
-  Problem* mutable_problem() { return &problem_; }
-  Solver::Options* mutable_solver_options() { return &options_; }
-
-  int num_cameras()            const { return num_cameras_;        }
-  int num_points()             const { return num_points_;         }
-  int num_observations()       const { return num_observations_;   }
-  const int* point_index()     const { return point_index_;  }
-  const int* camera_index()    const { return camera_index_; }
-  const double* observations() const { return observations_; }
-  double* mutable_cameras() { return parameters_; }
-  double* mutable_points() { return parameters_  + 9 * num_cameras_; }
-
- private:
-  void ReadData(const string& filename) {
-    FILE * fptr = fopen(filename.c_str(), "r");
-
-    if (!fptr) {
-      LOG(FATAL) << "File Error: unable to open file " << filename;
-    }
-
-    // This will die horribly on invalid files. Them's the breaks.
-    FscanfOrDie(fptr, "%d", &num_cameras_);
-    FscanfOrDie(fptr, "%d", &num_points_);
-    FscanfOrDie(fptr, "%d", &num_observations_);
-
-    VLOG(1) << "Header: " << num_cameras_
-            << " " << num_points_
-            << " " << num_observations_;
-
-    point_index_ = new int[num_observations_];
-    camera_index_ = new int[num_observations_];
-    observations_ = new double[2 * num_observations_];
-
-    num_parameters_ = 9 * num_cameras_ + 3 * num_points_;
-    parameters_ = new double[num_parameters_];
-
-    for (int i = 0; i < num_observations_; ++i) {
-      FscanfOrDie(fptr, "%d", camera_index_ + i);
-      FscanfOrDie(fptr, "%d", point_index_ + i);
-      for (int j = 0; j < 2; ++j) {
-        FscanfOrDie(fptr, "%lf", observations_ + 2*i + j);
-      }
-    }
-
-    for (int i = 0; i < num_parameters_; ++i) {
-      FscanfOrDie(fptr, "%lf", parameters_ + i);
-    }
-  }
-
-  void BuildProblem() {
-    double* points = mutable_points();
-    double* cameras = mutable_cameras();
-
-    for (int i = 0; i < num_observations(); ++i) {
-      // Each Residual block takes a point and a camera as input and
-      // outputs a 2 dimensional residual.
-      CostFunction* cost_function =
-          new AutoDiffCostFunction<BundlerResidual, 2, 9, 3>(
-              new BundlerResidual(observations_[2*i + 0],
-                                  observations_[2*i + 1]));
-
-      // Each observation correponds to a pair of a camera and a point
-      // which are identified by camera_index()[i] and
-      // point_index()[i] respectively.
-      double* camera = cameras + 9 * camera_index_[i];
-      double* point = points + 3 * point_index()[i];
-      problem_.AddResidualBlock(cost_function, NULL, camera, point);
-    }
-
-    options_.linear_solver_ordering.reset(new ParameterBlockOrdering);
-
-    // The points come before the cameras.
-    for (int i = 0; i < num_points_; ++i) {
-      options_.linear_solver_ordering->AddElementToGroup(points + 3 * i, 0);
-    }
-
-    for (int i = 0; i < num_cameras_; ++i) {
-      options_.linear_solver_ordering->AddElementToGroup(cameras + 9 * i, 1);
-    }
-
-    options_.max_num_iterations = 25;
-    options_.function_tolerance = 1e-10;
-    options_.gradient_tolerance = 1e-10;
-    options_.parameter_tolerance = 1e-10;
-  }
-
-  template<typename T>
-  void FscanfOrDie(FILE *fptr, const char *format, T *value) {
-    int num_scanned = fscanf(fptr, format, value);
-    if (num_scanned != 1) {
-      LOG(FATAL) << "Invalid UW data file.";
-    }
-  }
-
-  // Templated pinhole camera model.  The camera is parameterized
-  // using 9 parameters. 3 for rotation, 3 for translation, 1 for
-  // focal length and 2 for radial distortion. The principal point is
-  // not modeled (i.e. it is assumed be located at the image center).
-  struct BundlerResidual {
-    // (u, v): the position of the observation with respect to the image
-    // center point.
-    BundlerResidual(double u, double v): u(u), v(v) {}
-
-    template <typename T>
-    bool operator()(const T* const camera,
-                    const T* const point,
-                    T* residuals) const {
-      T p[3];
-      AngleAxisRotatePoint(camera, point, p);
-
-      // Add the translation vector
-      p[0] += camera[3];
-      p[1] += camera[4];
-      p[2] += camera[5];
-
-      const T& focal = camera[6];
-      const T& l1 = camera[7];
-      const T& l2 = camera[8];
-
-      // Compute the center of distortion.  The sign change comes from
-      // the camera model that Noah Snavely's Bundler assumes, whereby
-      // the camera coordinate system has a negative z axis.
-      T xp = - focal * p[0] / p[2];
-      T yp = - focal * p[1] / p[2];
-
-      // Apply second and fourth order radial distortion.
-      T r2 = xp*xp + yp*yp;
-      T distortion = T(1.0) + r2  * (l1 + l2  * r2);
-
-      residuals[0] = distortion * xp - T(u);
-      residuals[1] = distortion * yp - T(v);
-
-      return true;
-    }
-
-    double u;
-    double v;
-  };
-
-
-  Problem problem_;
-  Solver::Options options_;
-
-  int num_cameras_;
-  int num_points_;
-  int num_observations_;
-  int num_parameters_;
-
-  int* point_index_;
-  int* camera_index_;
-  double* observations_;
-  // The parameter vector is laid out as follows
-  // [camera_1, ..., camera_n, point_1, ..., point_m]
-  double* parameters_;
-};
-
-TEST(SystemTest, BundleAdjustmentProblem) {
-  vector<SolverConfig> configs;
-
-#define CONFIGURE(linear_solver, sparse_linear_algebra_library_type, ordering, preconditioner) \
-  configs.push_back(SolverConfig(linear_solver,                         \
-                                 sparse_linear_algebra_library_type,    \
-                                 ordering,                              \
-                                 preconditioner))
-
-  CONFIGURE(DENSE_SCHUR,            SUITE_SPARSE, kAutomaticOrdering, IDENTITY);
-  CONFIGURE(DENSE_SCHUR,            SUITE_SPARSE, kUserOrdering,      IDENTITY);
-
-  CONFIGURE(CGNR,                   SUITE_SPARSE, kAutomaticOrdering, JACOBI);
-
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kUserOrdering,      JACOBI);
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kAutomaticOrdering, JACOBI);
-
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kUserOrdering,      SCHUR_JACOBI);
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kAutomaticOrdering, SCHUR_JACOBI);
+TEST_F(PowellTest, IterativeSchurWithJacobi) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(ITERATIVE_SCHUR, NO_SPARSE, kAutomaticOrdering, JACOBI));
+}
 
 #ifndef CERES_NO_SUITESPARSE
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, kAutomaticOrdering, IDENTITY);
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, kUserOrdering,      IDENTITY);
-
-  CONFIGURE(SPARSE_SCHUR,           SUITE_SPARSE, kAutomaticOrdering, IDENTITY);
-  CONFIGURE(SPARSE_SCHUR,           SUITE_SPARSE, kUserOrdering,      IDENTITY);
-
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kAutomaticOrdering, CLUSTER_JACOBI);
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kUserOrdering,      CLUSTER_JACOBI);
-
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kAutomaticOrdering, CLUSTER_TRIDIAGONAL);
-  CONFIGURE(ITERATIVE_SCHUR,        SUITE_SPARSE, kUserOrdering,      CLUSTER_TRIDIAGONAL);
+TEST_F(PowellTest, SparseNormalCholeskyUsingSuiteSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, kAutomaticOrdering));
+}
 #endif  // CERES_NO_SUITESPARSE
 
 #ifndef CERES_NO_CXSPARSE
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE,    kAutomaticOrdering, IDENTITY);
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE,    kUserOrdering,      IDENTITY);
-
-  CONFIGURE(SPARSE_SCHUR,           CX_SPARSE,    kAutomaticOrdering, IDENTITY);
-  CONFIGURE(SPARSE_SCHUR,           CX_SPARSE,    kUserOrdering,      IDENTITY);
+TEST_F(PowellTest, SparseNormalCholeskyUsingCXSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, kAutomaticOrdering));
+}
 #endif  // CERES_NO_CXSPARSE
 
 #ifdef CERES_USE_EIGEN_SPARSE
-  CONFIGURE(SPARSE_SCHUR,           EIGEN_SPARSE, kAutomaticOrdering, IDENTITY);
-  CONFIGURE(SPARSE_SCHUR,           EIGEN_SPARSE, kUserOrdering,      IDENTITY);
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, EIGEN_SPARSE, kAutomaticOrdering, IDENTITY);
-  CONFIGURE(SPARSE_NORMAL_CHOLESKY, EIGEN_SPARSE, kUserOrdering,      IDENTITY);
-#endif  // CERES_USE_EIGEN_SPARSE
-
-#undef CONFIGURE
-
-  // Single threaded evaluators and linear solvers.
-  const double kMaxAbsoluteDifference = 1e-4;
-  RunSolversAndCheckTheyMatch<BundleAdjustmentProblem>(configs,
-                                                       kMaxAbsoluteDifference);
-
-#ifdef CERES_USE_OPENMP
-  // Multithreaded evaluators and linear solvers.
-  for (int i = 0; i < configs.size(); ++i) {
-    configs[i].num_threads = 2;
-  }
-  RunSolversAndCheckTheyMatch<BundleAdjustmentProblem>(configs,
-                                                       kMaxAbsoluteDifference);
-#endif  // CERES_USE_OPENMP
+TEST_F(PowellTest, SparseNormalCholeskyUsingEigenSparse) {
+  RunSolverForConfigAndExpectResidualsMatch(
+      SolverConfig(SPARSE_NORMAL_CHOLESKY, EIGEN_SPARSE, kAutomaticOrdering));
 }
+#endif  // CERES_USE_EIGEN_SPARSE
 
 }  // namespace internal
 }  // namespace ceres
diff --git a/internal/ceres/test_util.cc b/internal/ceres/test_util.cc
index 695d4d8..5edd4fc 100644
--- a/internal/ceres/test_util.cc
+++ b/internal/ceres/test_util.cc
@@ -36,6 +36,7 @@
 #include <cmath>
 #include "ceres/file.h"
 #include "ceres/stringprintf.h"
+#include "ceres/types.h"
 #include "gflags/gflags.h"
 #include "glog/logging.h"
 #include "gtest/gtest.h"
@@ -125,6 +126,18 @@
                   filename);
 }
 
+SolverConfig ThreadedSolverConfig(
+    LinearSolverType linear_solver_type,
+    SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
+    bool use_automatic_ordering,
+    PreconditionerType preconditioner_type) {
+  const int kNumThreads = 4;
+  return SolverConfig(linear_solver_type,
+                      sparse_linear_algebra_library_type,
+                      use_automatic_ordering,
+                      preconditioner_type,
+                      kNumThreads);
+}
 
 }  // namespace internal
 }  // namespace ceres
diff --git a/internal/ceres/test_util.h b/internal/ceres/test_util.h
index 65cc7dc..6aff541 100644
--- a/internal/ceres/test_util.h
+++ b/internal/ceres/test_util.h
@@ -30,6 +30,11 @@
 
 #include <string>
 #include "ceres/internal/port.h"
+#include "ceres/problem.h"
+#include "ceres/solver.h"
+#include "ceres/stringprintf.h"
+#include "gtest/gtest.h"
+
 
 #ifndef CERES_INTERNAL_TEST_UTIL_H_
 #define CERES_INTERNAL_TEST_UTIL_H_
@@ -65,6 +70,117 @@
 // local build/testing environment.
 std::string TestFileAbsolutePath(const std::string& filename);
 
+// Struct used for configuring the solver. Used by end-to-end tests.
+struct SolverConfig {
+  SolverConfig(
+      LinearSolverType linear_solver_type,
+      SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type = NO_SPARSE,
+      bool use_automatic_ordering = true,
+      PreconditionerType preconditioner_type = IDENTITY,
+      int num_threads = 1)
+      : linear_solver_type(linear_solver_type),
+        sparse_linear_algebra_library_type(sparse_linear_algebra_library_type),
+        use_automatic_ordering(use_automatic_ordering),
+        preconditioner_type(preconditioner_type),
+        num_threads(num_threads) {
+  }
+
+  std::string ToString() const {
+    return StringPrintf(
+        "(%s, %s, %s, %s, %d)",
+        LinearSolverTypeToString(linear_solver_type),
+        SparseLinearAlgebraLibraryTypeToString(
+            sparse_linear_algebra_library_type),
+        use_automatic_ordering ? "AUTOMATIC" : "USER",
+        PreconditionerTypeToString(preconditioner_type),
+        num_threads);
+  }
+
+  void UpdateOptions(Solver::Options* options) const {
+    options->linear_solver_type = linear_solver_type;
+    options->sparse_linear_algebra_library_type =
+        sparse_linear_algebra_library_type;
+    options->preconditioner_type = preconditioner_type;
+    options->num_threads = num_threads;
+    options->num_linear_solver_threads = num_threads;
+
+    if (use_automatic_ordering) {
+      options->linear_solver_ordering.reset();
+    }
+  }
+
+  LinearSolverType linear_solver_type;
+  SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type;
+  bool use_automatic_ordering;
+  PreconditionerType preconditioner_type;
+  int num_threads;
+};
+
+SolverConfig ThreadedSolverConfig(
+    LinearSolverType linear_solver_type,
+    SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type = NO_SPARSE,
+    bool use_automatic_ordering = true,
+    PreconditionerType preconditioner_type = IDENTITY);
+
+// A templated test fixture, that is used for testing Ceres end to end
+// by computing a solution to the problem for a given solver
+// configuration and comparing it to a reference solver configuration.
+//
+// It is assumed that the SystemTestProblem has an Solver::Options
+// struct that contains the reference Solver configuration.
+template <class SystemTestProblem>
+class SystemTest : public::testing::Test {
+ protected:
+  virtual void SetUp() {
+    SystemTestProblem system_test_problem;
+    SolveAndEvaluateFinalResiduals(
+        *system_test_problem.mutable_solver_options(),
+        system_test_problem.mutable_problem(),
+        &expected_final_residuals_);
+  }
+
+  void RunSolverForConfigAndExpectResidualsMatch(const SolverConfig& config) {
+    LOG(INFO) << "Running solver configuration: "
+              << config.ToString();
+
+    SystemTestProblem system_test_problem;
+    config.UpdateOptions(system_test_problem.mutable_solver_options());
+    std::vector<double> final_residuals;
+    SolveAndEvaluateFinalResiduals(
+        *system_test_problem.mutable_solver_options(),
+        system_test_problem.mutable_problem(),
+        &final_residuals);
+
+    // We compare solutions by comparing their residual vectors. We do
+    // not compare parameter vectors because it is much more brittle
+    // and error prone to do so, since the same problem can have
+    // nearly the same residuals at two completely different positions
+    // in parameter space.
+    CHECK_EQ(expected_final_residuals_.size(), final_residuals.size());
+    for (int i = 0; i < final_residuals.size(); ++i) {
+      EXPECT_NEAR(final_residuals[i],
+                  expected_final_residuals_[i],
+                  SystemTestProblem::kResidualTolerance)
+          << "Not close enough residual:" << i;
+    }
+  }
+
+  void SolveAndEvaluateFinalResiduals(const Solver::Options& options,
+                                      Problem* problem,
+                                      std::vector<double>* final_residuals) {
+    Solver::Summary summary;
+    Solve(options, problem, &summary);
+    CHECK_NE(summary.termination_type, ceres::FAILURE);
+    problem->Evaluate(Problem::EvaluateOptions(),
+                      NULL,
+                      final_residuals,
+                      NULL,
+                      NULL);
+  }
+
+  std::vector<double> expected_final_residuals_;
+};
+
 }  // namespace internal
 }  // namespace ceres