Use a small, standalone testing framework instead of googletest.
authorNico Weber <nicolasweber@gmx.de>
Thu, 18 Sep 2014 02:48:26 +0000 (19:48 -0700)
committerNico Weber <nicolasweber@gmx.de>
Thu, 18 Sep 2014 02:48:26 +0000 (19:48 -0700)
Ninja currently uses googletest for testing. That makes building
ninja_test somewhat annoying since it requires that one passes
--with-gtest PATH to configure. It turns out just implementing the bits
of googletest that ninja uses needs about the same amount of code than
making the --with-gtest flag in configure.py work and making googletest
print test results in a way we want (!)

In addition to making configuration simpler, this also makes compiling
tests much faster: On my system, touching src/build_test.cc (the slowest
file to build in ninja) and rebuilding ninja_tests is twice as fast than
without this patch. Building all is noticeably faster too: 5.6s with
this patch, 9.1s without this patch (38% faster).

The most noticeable things missing: EXPECT_* and ASSERT_* don't support
streaming notes to them with operator<<, and for failing tests the lhs
and rhs are not printed. That's so that this header does not have to
include sstream, which slows down building ninja_test almost 20%.
If this turns out to be annoying, we can maybe add it.

14 files changed:
configure.py
src/build_test.cc
src/depfile_parser_test.cc
src/deps_log_test.cc
src/disk_interface_test.cc
src/includes_normalize_test.cc
src/lexer_test.cc
src/manifest_parser_test.cc
src/msvc_helper_test.cc
src/ninja_test.cc
src/state_test.cc
src/subprocess_test.cc
src/test.cc
src/test.h

index 64123a0..a307043 100755 (executable)
@@ -44,8 +44,7 @@ parser.add_option('--debug', action='store_true',
 parser.add_option('--profile', metavar='TYPE',
                   choices=profilers,
                   help='enable profiling (' + '/'.join(profilers) + ')',)
-parser.add_option('--with-gtest', metavar='PATH',
-                  help='use gtest unpacked in directory PATH')
+parser.add_option('--with-gtest', metavar='PATH', help='ignored')
 parser.add_option('--with-python', metavar='EXE',
                   help='use EXE as the Python interpreter',
                   default=os.path.basename(sys.executable))
@@ -319,34 +318,10 @@ all_targets += ninja
 n.comment('Tests all build into ninja_test executable.')
 
 variables = []
-test_cflags = cflags + ['-DGTEST_HAS_RTTI=0']
 test_ldflags = None
 test_libs = libs
 objs = []
-if options.with_gtest:
-    path = options.with_gtest
 
-    gtest_all_incs = '-I%s -I%s' % (path, os.path.join(path, 'include'))
-    if platform.is_msvc():
-        gtest_cflags = '/nologo /EHsc /Zi /D_VARIADIC_MAX=10 '
-        if platform.msvc_needs_fs():
-          gtest_cflags += '/FS '
-        gtest_cflags += gtest_all_incs
-    else:
-        gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs
-    objs += n.build(built('gtest-all' + objext), 'cxx',
-                    os.path.join(path, 'src', 'gtest-all.cc'),
-                    variables=[('cflags', gtest_cflags)])
-
-    test_cflags.append('-I%s' % os.path.join(path, 'include'))
-else:
-    # Use gtest from system.
-    if platform.is_msvc():
-        test_libs.extend(['gtest_main.lib', 'gtest.lib'])
-    else:
-        test_libs.extend(['-lgtest_main', '-lgtest'])
-
-n.variable('test_cflags', test_cflags)
 for name in ['build_log_test',
              'build_test',
              'clean_test',
@@ -362,10 +337,10 @@ for name in ['build_log_test',
              'subprocess_test',
              'test',
              'util_test']:
-    objs += cxx(name, variables=[('cflags', '$test_cflags')])
+    objs += cxx(name)
 if platform.is_windows():
     for name in ['includes_normalize_test', 'msvc_helper_test']:
-        objs += cxx(name, variables=[('cflags', '$test_cflags')])
+        objs += cxx(name)
 
 if not platform.is_windows():
     test_libs.append('-lpthread')
index dad69dc..6336206 100644 (file)
@@ -14,6 +14,8 @@
 
 #include "build.h"
 
+#include <assert.h>
+
 #include "build_log.h"
 #include "deps_log.h"
 #include "graph.h"
@@ -506,7 +508,7 @@ void BuildTest::RebuildTarget(const string& target, const char* manifest,
   builder.command_runner_.reset(&command_runner_);
   if (!builder.AlreadyUpToDate()) {
     bool build_res = builder.Build(&err);
-    EXPECT_TRUE(build_res) << "builder.Build(&err)";
+    EXPECT_TRUE(build_res);
   }
   builder.command_runner_.release();
 }
index a5f3321..e67ef79 100644 (file)
@@ -14,7 +14,7 @@
 
 #include "depfile_parser.h"
 
-#include <gtest/gtest.h>
+#include "test.h"
 
 struct DepfileParserTest : public testing::Test {
   bool Parse(const char* input, string* err);
index e8e5138..4fa4008 100644 (file)
 
 #include "deps_log.h"
 
+#ifndef _WIN32
+#include <unistd.h>
+#include <sys/stat.h>
+#endif
+
 #include "graph.h"
 #include "util.h"
 #include "test.h"
index b2e8cb5..05d509c 100644 (file)
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <gtest/gtest.h>
-
+#include <assert.h>
+#include <stdio.h>
 #ifdef _WIN32
 #include <io.h>
 #include <windows.h>
index 419996f..cf4a4a3 100644 (file)
@@ -14,8 +14,6 @@
 
 #include "includes_normalize.h"
 
-#include <gtest/gtest.h>
-
 #include "test.h"
 #include "util.h"
 
index e8a1642..331d8e1 100644 (file)
@@ -14,9 +14,8 @@
 
 #include "lexer.h"
 
-#include <gtest/gtest.h>
-
 #include "eval_env.h"
+#include "test.h"
 
 TEST(Lexer, ReadVarValue) {
   Lexer lexer("plain text $var $VaR ${x}\n");
index 152b965..ee87c94 100644 (file)
 #include <map>
 #include <vector>
 
-#include <gtest/gtest.h>
-
 #include "graph.h"
 #include "state.h"
+#include "test.h"
 
 struct ParserTest : public testing::Test,
                     public ManifestParser::FileReader {
   void AssertParse(const char* input) {
     ManifestParser parser(&state, this);
     string err;
-    ASSERT_TRUE(parser.ParseTest(input, &err)) << err;
+    EXPECT_TRUE(parser.ParseTest(input, &err));
     ASSERT_EQ("", err);
   }
 
index 391c045..29db650 100644 (file)
@@ -14,8 +14,6 @@
 
 #include "msvc_helper.h"
 
-#include <gtest/gtest.h>
-
 #include "test.h"
 #include "util.h"
 
index 989ea5c..9499c8b 100644 (file)
 #include <stdarg.h>
 #include <stdio.h>
 
-#include "gtest/gtest.h"
+#include "test.h"
 #include "line_printer.h"
 
+// This can't be a vector because tests call RegisterTest from static
+// initializers and the order static initializers run it isn't specified. So
+// the vector constructor isn't guaranteed to run before all of the
+// RegisterTest() calls.
+static testing::Test* (*tests[10000])();
+testing::Test* g_current_test;
+static int ntests;
+static LinePrinter printer;
+
+void RegisterTest(testing::Test* (*factory)()) {
+  tests[ntests++] = factory;
+}
+
 string StringPrintf(const char* format, ...) {
   const int N = 1024;
   char buf[N];
@@ -30,59 +43,37 @@ string StringPrintf(const char* format, ...) {
   return buf;
 }
 
-/// A test result printer that's less wordy than gtest's default.
-struct LaconicPrinter : public testing::EmptyTestEventListener {
-  LaconicPrinter() : tests_started_(0), test_count_(0), iteration_(0) {}
-  virtual void OnTestProgramStart(const testing::UnitTest& unit_test) {
-    test_count_ = unit_test.test_to_run_count();
-  }
-
-  virtual void OnTestIterationStart(const testing::UnitTest& test_info,
-                                    int iteration) {
-    tests_started_ = 0;
-    iteration_ = iteration;
-  }
-
-  virtual void OnTestStart(const testing::TestInfo& test_info) {
-    ++tests_started_;
-    printer_.Print(
-        StringPrintf("[%d/%d%s] %s.%s",
-                     tests_started_,
-                     test_count_,
-                     iteration_ ? StringPrintf(" iter %d", iteration_).c_str()
-                                : "",
-                     test_info.test_case_name(),
-                     test_info.name()),
-        LinePrinter::ELIDE);
+bool testing::Test::Check(bool condition, const char* file, int line,
+                          const char* error) {
+  if (!condition) {
+    printer.PrintOnNewLine(
+        StringPrintf("*** Failure in %s:%d\n%s\n", file, line, error));
+    failed_ = true;
   }
+  return condition;
+}
 
-  virtual void OnTestPartResult(
-      const testing::TestPartResult& test_part_result) {
-    if (!test_part_result.failed())
-      return;
-    printer_.PrintOnNewLine(StringPrintf(
-        "*** Failure in %s:%d\n%s\n", test_part_result.file_name(),
-        test_part_result.line_number(), test_part_result.summary()));
-  }
+int main(int argc, char **argv) {
+  int tests_started = 0;
 
-  virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) {
-    printer_.PrintOnNewLine(unit_test.Passed() ? "passed\n" : "failed\n");
-  }
+  bool passed = true;
+  for (int i = 0; i < ntests; i++) {
+    ++tests_started;
 
- private:
-  LinePrinter printer_;
-  int tests_started_;
-  int test_count_;
-  int iteration_;
-};
+    testing::Test* test = tests[i]();
 
-int main(int argc, char **argv) {
-  testing::InitGoogleTest(&argc, argv);
+    printer.Print(
+        StringPrintf("[%d/%d] %s", tests_started, ntests, test->Name()),
+        LinePrinter::ELIDE);
 
-  testing::TestEventListeners& listeners =
-      testing::UnitTest::GetInstance()->listeners();
-  delete listeners.Release(listeners.default_result_printer());
-  listeners.Append(new LaconicPrinter);
+    test->SetUp();
+    test->Run();
+    test->TearDown();
+    if (test->Failed())
+      passed = false;
+    delete test;
+  }
 
-  return RUN_ALL_TESTS();
+  printer.PrintOnNewLine(passed ? "passed\n" : "failed\n");
+  return passed ? EXIT_SUCCESS : EXIT_FAILURE;
 }
index af2bff1..a4fafa1 100644 (file)
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <gtest/gtest.h>
-
 #include "graph.h"
 #include "state.h"
+#include "test.h"
 
 namespace {
 
index 775a13a..fe53748 100644 (file)
@@ -18,6 +18,7 @@
 
 #ifndef _WIN32
 // SetWithLots need setrlimit.
+#include <stdio.h>
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <unistd.h>
@@ -92,7 +93,7 @@ TEST_F(SubprocessTest, InterruptParent) {
       return;
   }
 
-  ADD_FAILURE() << "We should have been interrupted";
+  ASSERT_FALSE("We should have been interrupted");
 }
 
 TEST_F(SubprocessTest, Console) {
@@ -176,9 +177,10 @@ TEST_F(SubprocessTest, SetWithLots) {
   // Make sure [ulimit -n] isn't going to stop us from working.
   rlimit rlim;
   ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlim));
-  ASSERT_GT(rlim.rlim_cur, kNumProcs)
-      << "Raise [ulimit -n] well above " << kNumProcs
-      << " to make this test go";
+  if (!EXPECT_GT(rlim.rlim_cur, kNumProcs)) {
+    printf("Raise [ulimit -n] well above %u to make this test go\n", kNumProcs);
+    return;
+  }
 
   vector<Subprocess*> procs;
   for (size_t i = 0; i < kNumProcs; ++i) {
index 21015ed..ed2b910 100644 (file)
@@ -24,6 +24,8 @@
 
 #ifdef _WIN32
 #include <windows.h>
+#else
+#include <unistd.h>
 #endif
 
 namespace {
@@ -90,7 +92,7 @@ Node* StateTestWithBuiltinRules::GetNode(const string& path) {
 void AssertParse(State* state, const char* input) {
   ManifestParser parser(state, NULL);
   string err;
-  ASSERT_TRUE(parser.ParseTest(input, &err)) << err;
+  EXPECT_TRUE(parser.ParseTest(input, &err));
   ASSERT_EQ("", err);
 }
 
index f34b877..be5dcff 100644 (file)
 #ifndef NINJA_TEST_H_
 #define NINJA_TEST_H_
 
-#include <gtest/gtest.h>
-
 #include "disk_interface.h"
 #include "state.h"
 #include "util.h"
 
+// A tiny testing framework inspired by googletest, but much simpler and
+// faster to compile. It supports most things commonly used from googltest. The
+// most noticeable things missing: EXPECT_* and ASSERT_* don't support
+// streaming notes to them with operator<<, and for failing tests the lhs and
+// rhs are not printed. That's so that this header does not have to include
+// sstream, which slows down building ninja_test almost 20%.
+namespace testing {
+class Test {
+  bool failed_;
+  int assertion_failures_;
+ public:
+  Test() : failed_(false), assertion_failures_(0) {}
+  virtual ~Test() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+  virtual void Run() = 0;
+  virtual const char* Name() const = 0;
+
+  bool Failed() const { return failed_; }
+  int AssertionFailures() const { return assertion_failures_; }
+  void AddAssertionFailure() { assertion_failures_++; }
+  bool Check(bool condition, const char* file, int line, const char* error);
+};
+}
+
+void RegisterTest(testing::Test* (*)());
+
+extern testing::Test* g_current_test;
+#define TEST_F_(x, y, name)                                           \
+  struct y : public x {                                               \
+    static testing::Test* Create() { return g_current_test = new y; } \
+    virtual void Run();                                               \
+    virtual const char* Name() const { return name; }                 \
+  };                                                                  \
+  struct Register##y {                                                \
+    Register##y() { RegisterTest(y::Create); }                        \
+  };                                                                  \
+  Register##y g_register_##y;                                         \
+  void y::Run()
+
+#define TEST_F(x, y) TEST_F_(x, x##y, #x "." #y)
+#define TEST(x, y) TEST_F_(testing::Test, x##y, #x "." #y)
+
+#define EXPECT_EQ(a, b) \
+  g_current_test->Check(a == b, __FILE__, __LINE__, #a " == " #b)
+#define EXPECT_NE(a, b) \
+  g_current_test->Check(a != b, __FILE__, __LINE__, #a " != " #b)
+#define EXPECT_GT(a, b) \
+  g_current_test->Check(a > b, __FILE__, __LINE__, #a " > " #b)
+#define EXPECT_LT(a, b) \
+  g_current_test->Check(a < b, __FILE__, __LINE__, #a " < " #b)
+#define EXPECT_GE(a, b) \
+  g_current_test->Check(a >= b, __FILE__, __LINE__, #a " >= " #b)
+#define EXPECT_LE(a, b) \
+  g_current_test->Check(a <= b, __FILE__, __LINE__, #a " <= " #b)
+#define EXPECT_TRUE(a) \
+  g_current_test->Check(static_cast<bool>(a), __FILE__, __LINE__, #a)
+#define EXPECT_FALSE(a) \
+  g_current_test->Check(!static_cast<bool>(a), __FILE__, __LINE__, #a)
+
+#define ASSERT_EQ(a, b) \
+  if (!EXPECT_EQ(a, b)) { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_NE(a, b) \
+  if (!EXPECT_NE(a, b)) { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_GT(a, b) \
+  if (!EXPECT_GT(a, b)) { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_LT(a, b) \
+  if (!EXPECT_LT(a, b)) { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_GE(a, b) \
+  if (!EXPECT_GE(a, b)) { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_LE(a, b) \
+  if (!EXPECT_LE(a, b)) { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_TRUE(a)  \
+  if (!EXPECT_TRUE(a))  { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_FALSE(a) \
+  if (!EXPECT_FALSE(a)) { g_current_test->AddAssertionFailure(); return; }
+#define ASSERT_NO_FATAL_FAILURE(a)                  \
+  {                                                 \
+    int f = g_current_test->AssertionFailures();    \
+    a;                                              \
+    if (f != g_current_test->AssertionFailures()) { \
+      g_current_test->AddAssertionFailure();        \
+      return;                                       \
+    }                                               \
+  }
+
 // Support utilites for tests.
 
 struct Node;