blob: b03d90209f22ddba0afcc6aaa5c9e9c2d1e36541 [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NINJA_TEST_H_
#define NINJA_TEST_H_
#include <stddef.h>
#include "disk_interface.h"
#include "manifest_parser.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;
bool Failed() const { return failed_; }
int AssertionFailures() const { return assertion_failures_; }
void AddAssertionFailure() { assertion_failures_++; }
bool NullFailure(bool expected, const char* file, int line, const char* error,
const char* value);
bool BooleanFailure(bool expected, const char* file, int line,
const char* error, const char* value);
bool BinopFailure(const char* file, int line, const char* error, const char* first_value, const char* second_value);
};
/// std::void_t<T> is only available since C++17, so use
/// ::testing::Void<T>::type for the same thing. Used by AsString<> below.
template <class ...>
struct Void {
using type = void;
};
/// Expand to a type expression that is only valid if |expression| compiles
/// properly. Only useful for C++ SFINAE.
#define TESTING_ENABLE_IF_EXPRESSION_COMPILES(expression) \
typename ::testing::Void<decltype(expression)>::type
/// Expand to a type expression that is only valid if type T provides
/// an AsString() method. Note: for simplicity, there is no check that
/// the result is convertible into an std::string here.
#define TESTING_ENABLE_IF_METHOD_AS_STRING_EXISTS_FOR_TYPE(T) \
TESTING_ENABLE_IF_EXPRESSION_COMPILES(std::declval<T>().AsString())
/// Expand to a type expression that is only valid if type T is supported
/// as an std::to_string() argument type.
#define TESTING_ENABLE_IF_STD_TO_STRING_SUPPORTS_TYPE(T) \
TESTING_ENABLE_IF_EXPRESSION_COMPILES(std::to_string(std::declval<T>()))
/// RemoveCVRef<T> remove references as well as const and volatile modifier.
/// Used by AsString<> below.
template <typename T>
struct RemoveCVRef {
using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
};
/// Expand to the base type of expression value |v|, which means without
/// any reference, const or volatile qualifier. This can be used as a
/// type argument when defining template specializations.
#define TESTING_BASE_TYPE_FOR_VALUE(v) \
typename ::testing::RemoveCVRef<decltype(v)>::type
/// ConstRefT<T>::type is a const reference type that binds to a value of type
/// T.
template <typename T>
struct ConstRefT {
using type = const typename std::remove_const<
typename std::remove_reference<T>::type>::type&;
};
#define TESTING_CONST_REF_TYPE_FOR_VALUE(v) \
typename ::testing::ConstRefT<decltype(v)>::type
/// ::testing::AsString<T> is a helper struct used to convert expression
/// in EXPECT_XXX and ASSERT_XXX macros into the string representation of
/// their value. This uses C++11 template metadata magic which can be easily
/// confusing, so as a short technical note:
///
/// - An AsString<T> value is an object that provides a `c_str()` method that
/// will be used to print it in case of test failure.
///
/// - By default, AsString<T>::c_str() will return `"<UNPRINTABLE>"`. This
/// would happen when T is an std::pair<> or something more complex.
///
/// - If type T has an AsString() method, it will be used automatically
/// to retrieve a printable version of the value. E.g. StringPiece!
///
/// - If type T is supported by std::to_string(), the latter is used
/// automatically as well (so all integral and floating point values).
///
/// - AsString<T*>::c_str() will return an hexadecimal address (just like %p)
///
/// The generic / fallback version.
template <typename T, typename Enable = void>
struct AsString {
AsString(const T& value) {}
const char* c_str() const { return "<UNPRINTABLE>"; }
};
/// Print booleans as either "true" or "false".
template <>
struct AsString<bool, void> {
AsString(bool value) : value_(value) {}
const char* c_str() const { return value_ ? "true" : "false"; }
bool value_;
};
/// Print pointers with %p by default. See implementation in ninja_test.cc
template <>
struct AsString<void*, void> {
AsString(const void* ptr);
const char* c_str() const { return str_.c_str(); }
std::string str_;
};
template <typename T>
struct AsString<T*, void> : public AsString<void*, void> {
AsString(const T* ptr) : AsString<void*, void>(ptr) {}
};
/// Anything supported by std::to_string() is supported implicitly too.
template <typename T>
struct AsString<T, TESTING_ENABLE_IF_STD_TO_STRING_SUPPORTS_TYPE(T)> {
AsString(const T value) : str_(std::to_string(value)) {}
const char* c_str() const { return str_.c_str(); }
std::string str_;
};
/// Anything that has an AsString() method is supported implicitly too.
/// For example, StringPiece!
template <typename T>
struct AsString<T, TESTING_ENABLE_IF_METHOD_AS_STRING_EXISTS_FOR_TYPE(T)> {
AsString(const T& value) : str_(value.AsString()) {}
const char* c_str() const { return str_.c_str(); }
std::string str_;
};
/// Support for std::string, avoids a copy.
template <>
struct AsString<std::string> {
AsString(const std::string& value) : str_(value) {}
const char* c_str() const { return str_.c_str(); }
const std::string& str_;
};
/// Support for literal C strings.
template <size_t N>
struct AsString<char [N], void> {
AsString(const char* value) : value_(value) {}
const char* c_str() const { return value_; }
const char* value_;
};
/// Support for C string pointers.
template <>
struct AsString<const char*> {
AsString(const char* value) : value_(value) {}
const char* c_str() const { return value_; }
const char* value_;
};
template <>
struct AsString<char*> : public AsString<const char*> {
AsString(const char* value) : AsString<const char*>(value) {}
};
}
void RegisterTest(testing::Test* (*)(), const char*);
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(); \
}; \
struct Register##y { \
Register##y() { RegisterTest(y::Create, name); } \
}; \
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)
/// Generic EXPECT macro for binary operations. The use of a lambda here
/// is necessary to ensure that the expressions in (a) and (b) are only
/// evaluated once at runtime in case of failure.
#define EXPECT_BINARY_OP(a, b, op) \
([](TESTING_CONST_REF_TYPE_FOR_VALUE(a) va, \
TESTING_CONST_REF_TYPE_FOR_VALUE(b) vb) -> bool { \
return ((va op vb) \
? true \
: g_current_test->BinopFailure( \
__FILE__, __LINE__, #a " " #op " " #b, \
::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(va)>(va) \
.c_str(), \
::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(vb)>(vb) \
.c_str())); \
}(a, b))
#define EXPECT_EQ(a, b) EXPECT_BINARY_OP(a, b, ==)
#define EXPECT_NE(a, b) EXPECT_BINARY_OP(a, b, !=)
#define EXPECT_GT(a, b) EXPECT_BINARY_OP(a, b, >)
#define EXPECT_LT(a, b) EXPECT_BINARY_OP(a, b, <)
#define EXPECT_GE(a, b) EXPECT_BINARY_OP(a, b, >=)
#define EXPECT_LE(a, b) EXPECT_BINARY_OP(a, b, <=)
/// Generic EXPECT macro for boolean comparisons.
#define EXPECT_BOOLEAN_OP(a, expected) \
([](TESTING_CONST_REF_TYPE_FOR_VALUE(a) va) -> bool { \
return (static_cast<bool>(va) == expected) \
? true \
: g_current_test->BooleanFailure( \
expected, __FILE__, __LINE__, #a, \
::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(va)>(va) \
.c_str()); \
}(a))
#define EXPECT_TRUE(a) EXPECT_BOOLEAN_OP(a, true)
#define EXPECT_FALSE(a) EXPECT_BOOLEAN_OP(a, false)
/// Generic EXPECT macro for pointer nullptr comparisons.
/// Note that boolean comparisons should work too, but the message in case
/// of failure is slightly clearer when using this macro.
#define EXPECT_NULL_OP(a, expected) \
([](TESTING_CONST_REF_TYPE_FOR_VALUE(a) va) -> bool { \
return (expected == ((va) == nullptr)) \
? true \
: g_current_test->NullFailure( \
expected, __FILE__, __LINE__, #a, \
::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(va)>(va) \
.c_str()); \
}(a))
#define EXPECT_NULL(a) EXPECT_NULL_OP(a, true)
#define EXPECT_NOT_NULL(a) EXPECT_NULL_OP(a, false)
#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_NULL(a) \
if (!EXPECT_NULL(a)) { \
g_current_test->AddAssertionFailure(); \
return; \
}
#define ASSERT_NOT_NULL(a) \
if (!EXPECT_NOT_NULL(a)) { \
g_current_test->AddAssertionFailure(); \
return; \
}
#define ASSERT_NO_FATAL_FAILURE(a) \
{ \
int fail_count = g_current_test->AssertionFailures(); \
a; \
if (fail_count != g_current_test->AssertionFailures()) { \
g_current_test->AddAssertionFailure(); \
return; \
} \
}
// Support utilities for tests.
struct Node;
/// A base test fixture that includes a State object with a
/// builtin "cat" rule.
struct StateTestWithBuiltinRules : public testing::Test {
StateTestWithBuiltinRules();
/// Add a "cat" rule to \a state. Used by some tests; it's
/// otherwise done by the ctor to state_.
void AddCatRule(State* state);
/// Short way to get a Node by its path from state_.
Node* GetNode(const std::string& path);
State state_;
};
void AssertParse(State* state, const char* input,
ManifestParserOptions = ManifestParserOptions());
void AssertHash(const char* expected, uint64_t actual);
void VerifyGraph(const State& state);
/// An implementation of DiskInterface that uses an in-memory representation
/// of disk state. It also logs file accesses and directory creations
/// so it can be used by tests to verify disk access patterns.
struct VirtualFileSystem : public DiskInterface {
VirtualFileSystem() : now_(1) {}
/// "Create" a file with contents.
void Create(const std::string& path, const std::string& contents);
/// Tick "time" forwards; subsequent file operations will be newer than
/// previous ones.
int Tick() {
return ++now_;
}
// DiskInterface
virtual TimeStamp Stat(const std::string& path, std::string* err) const;
virtual bool WriteFile(const std::string& path, const std::string& contents);
virtual bool MakeDir(const std::string& path);
virtual Status ReadFile(const std::string& path, std::string* contents,
std::string* err);
virtual int RemoveFile(const std::string& path);
/// An entry for a single in-memory file.
struct Entry {
int mtime;
std::string stat_error; // If mtime is -1.
std::string contents;
};
std::vector<std::string> directories_made_;
std::vector<std::string> files_read_;
typedef std::map<std::string, Entry> FileMap;
FileMap files_;
std::set<std::string> files_removed_;
std::set<std::string> files_created_;
/// A simple fake timestamp for file operations.
int now_;
};
struct ScopedTempDir {
ScopedTempDir();
~ScopedTempDir();
/// Create a temporary directory and chdir into it.
void CreateAndEnter(const std::string& name);
/// Clean up the temporary directory.
void Cleanup();
/// The current directory when creating this instance.
std::string original_dir_;
/// The subdirectory name for our dir, or empty if it hasn't been set up.
std::string temp_dir_name_;
};
#endif // NINJA_TEST_H_