Skip to content
Snippets Groups Projects
gtest_lite.h 18.14 KiB
#ifndef GTEST_LITE_H
#define GTEST_LITE_H

/**
 * \file gtest_lite.h  (v3/2019)
 *
 * Google gtest keretrendszerhez hasonló rendszer.
 * Sz.I. 2015., 2016., 2017. (_Has_X)
 * Sz.I. 2018 (template), ENDM, ENDMsg, nullptr_t
 * Sz.I. 2019 singleton
 * Sz.I. 2021 ASSERT.., STRCASE...
 * Sz.I. 2021 EXPEXT_REGEXP
 *
 * A tesztelés legalapvetőbb funkcióit támogató függvények és makrók.
 * Nem szálbiztos megvalósítás.
 *
 *
 * Szabadon felhasználható, bővíthető.
 *
 * Használati példa:
 *   Teszteljük az f(x)=2*x függvényt:
 * int f(int x) { return 2*x; }
 *
 * int main() {
 *   TEST(TeszEsetNeve, TesztNeve)
 *     EXPECT_EQ(0, f(0));
 *     EXPECT_EQ(4, f(2)) << "A függvény hibás eredményt adott" << std::endl;
 *     ...
 *   END
 * ...
 * // Fatális hiba esetén a teszteset nem fut tovább. Ezek az ASSERT... makrók.
 * // Nem lehet a kiírásukhoz további üzenetet fűzni. PL:
 *   TEST(TeszEsetNeve, TesztNeve)
 *     ASSERT_NO_THROW(f(0));  // itt nem lehet << "duma"
 *     EXPECT_EQ(4, f(2)) << "A függvény hibás eredményt adott" << std::endl;
 *     ...
 *   END
 *  ...
 *
 * A működés részleteinek megértése szorgalmi feladat.
 */

#include <iostream>
#include <cassert>
#include <cmath>
#include <cstring>
#include <limits>
#include <cstdlib>
#include <string>
#include <fstream>
#if __cplusplus >= 201103L
# include <iterator>
# include <regex>
#endif
#ifdef MEMTRACE
# include "memtrace.h"
#endif

// Két makró az egyes tesztek elé és mögé:
// A két makró a kapcsos zárójelekkel egy új blokkot hoz létre, amiben
// a nevek lokálisak, így elkerülhető a névütközés.

/// Teszt kezdete. A makró paraméterezése hasonlít a gtest
/// paraméterezéséhez. Így az itt elkészített testek könnyen átemelhetők
/// a gtest keretrendszerbe.
/// @param C - teszteset neve (csak a gtest kompatibilitás miatt van külön neve az eseteknek)
/// @param N - teszt neve
#define TEST(C, N) do { gtest_lite::test.begin(#C"."#N);

/// Teszteset vége.
#define END gtest_lite::test.end(); } while (false);

/// Teszteset vége allokált blokkok számának összehasonlításával
/// Ez az ellenőrzés nem bomba biztos.
#define ENDM gtest_lite::test.end(true); } while (false);

/// Teszteset vége allokált blokkok számának összehasonlításával
/// Ez az ellenőrzés nem bomba biztos.
/// Ha hiba van kiírja az üzenetet.
#define ENDMsg(t) gtest_lite::test.end(true) << t << std::endl; } while (false);

// Eredmények vizsgálatát segítő makrók.
// A paraméterek és a funkciók a gtest keretrendszerrel megegyeznek.

/// Sikeres teszt makrója
#define SUCCEED() gtest_lite::test.expect(true, __FILE__, __LINE__, "SUCCEED()", true)

/// Sikertelen teszt fatális hiba makrója
#define FAIL() gtest_lite::test.expect(false, __FILE__, __LINE__, "FAIL()", true)

/// Sikertelen teszt makrója
#define ADD_FAILURE() gtest_lite::test.expect(false, __FILE__, __LINE__, "ADD_FAILURE()", true)

/// Azonosságot elváró makró
#define EXPECT_EQ(expected, actual) gtest_lite::EXPECT_(expected, actual, gtest_lite::eq, __FILE__, __LINE__, "EXPECT_EQ(" #expected ", " #actual ")" )

/// Eltérést elváró makró
#define EXPECT_NE(expected, actual) gtest_lite::EXPECT_(expected, actual, gtest_lite::ne, __FILE__, __LINE__, "EXPECT_NE(" #expected ", " #actual ")", "etalon" )

/// Kisebb, vagy egyenlő relációt elváró makró
#define EXPECT_LE(expected, actual) gtest_lite::EXPECT_(expected, actual, gtest_lite::le, __FILE__, __LINE__, "EXPECT_LE(" #expected ", " #actual ")", "etalon" )

/// Kisebb, mint relációt elváró makró
#define EXPECT_LT(expected, actual) gtest_lite::EXPECT_(expected, actual, gtest_lite::lt, __FILE__, __LINE__, "EXPECT_LT(" #expected ", " #actual ")", "etalon" )

/// Nagyobb, vagy egyenlő relációt elváró makró
#define EXPECT_GE(expected, actual) gtest_lite::EXPECT_(expected, actual, gtest_lite::ge, __FILE__, __LINE__, "EXPECT_GE(" #expected ", " #actual ")", "etalon" )

/// Nagyobb, mint relációt elváró makró
#define EXPECT_GT(expected, actual) gtest_lite::EXPECT_(expected, actual, gtest_lite::gt, __FILE__, __LINE__, "EXPECT_GT(" #expected ", " #actual ")", "etalon" )

/// Igaz értéket elváró makró
#define EXPECT_TRUE(actual)  gtest_lite::EXPECT_(true, actual,  gtest_lite::eq, __FILE__, __LINE__, "EXPECT_TRUE(" #actual ")" )

/// Hamis értéket elváró makró
#define EXPECT_FALSE(actual) gtest_lite::EXPECT_(false, actual, gtest_lite::eq, __FILE__, __LINE__, "EXPECT_FALSE(" #actual ")" )

/// Valós számok azonosságát elváró makró
#define EXPECT_FLOAT_EQ(expected, actual)  gtest_lite::EXPECT_(expected, actual, gtest_lite::almostEQ, __FILE__, __LINE__, "EXPECT_FLOAT_EQ(" #expected ", " #actual ")" )

/// Valós számok azonosságát elváró makró
#define EXPECT_DOUBLE_EQ(expected, actual) gtest_lite::EXPECT_(expected, actual, gtest_lite::almostEQ, __FILE__, __LINE__, "EXPECT_DOUBLE_EQ(" #expected ", " #actual ")" )

/// C stringek (const char *) azonosságát tesztelő makró
#define EXPECT_STREQ(expected, actual) gtest_lite::EXPECTSTR(expected, actual, gtest_lite::eqstr, __FILE__, __LINE__, "EXPECT_STREQ(" #expected ", " #actual ")" )

/// C stringek (const char *) eltéréset tesztelő makró
#define EXPECT_STRNE(expected, actual) gtest_lite::EXPECTSTR(expected, actual, gtest_lite::nestr, __FILE__, __LINE__, "EXPECT_STRNE(" #expected ", " #actual ")", "etalon" )

/// C stringek (const char *) azonosságát tesztelő makró (kisbetű/nagybetű azonos)
#define EXPECT_STRCASEEQ(expected, actual) gtest_lite::EXPECTSTR(expected, actual, gtest_lite::eqstrcase, __FILE__, __LINE__, "EXPECT_STRCASEEQ(" #expected ", " #actual ")" )

/// C stringek (const char *) eltéréset tesztelő makró (kisbetű/nagybetű azonos)
#define EXPECT_STRCASENE(expected, actual) gtest_lite::EXPECTSTR(expected, actual, gtest_lite::nestrcase, __FILE__, __LINE__, "EXPECT_STRCASENE(" #expected ", " #actual ")", "etalon" )

/// Kivételt várunk
#define EXPECT_THROW(statement, exception_type) try { gtest_lite::test.tmp = false; statement; } \
    catch (exception_type) { gtest_lite::test.tmp = true; } \
    catch (...) { } \
    EXPECTTHROW(statement, "kivetelt dob.", "nem dobott '"#exception_type"' kivetelt.")

/// Kivételt várunk
#define EXPECT_ANY_THROW(statement) try { gtest_lite::test.tmp = false; statement; } \
    catch (...) { gtest_lite::test.tmp = true; } \
    EXPECTTHROW(statement, "kivetelt dob.", "nem dobott kivetelt.")

/// Nem várunk kivételt
#define EXPECT_NO_THROW(statement) try { gtest_lite::test.tmp = true; statement; } \
    catch (...) { gtest_lite::test.tmp = false; }\
    EXPECTTHROW(statement, "nem dob kivetelt.", "kivetelt dobott.")

/// Nem várunk kivételt
#define ASSERT_NO_THROW(statement) try { gtest_lite::test.tmp = true; statement; } \
    catch (...) { gtest_lite::test.tmp = false; }\
    ASSERTTHROW(statement, "nem dob kivetelt.", "kivetelt dobott.")

/// Kivételt várunk és továbbdobjuk -- ilyen nincs a gtest-ben
#define EXPECT_THROW_THROW(statement, exception_type) try { gtest_lite::test.tmp = false; statement; } \
    catch (exception_type) { gtest_lite::test.tmp = true; throw; } \
    EXPECTTHROW(statement, "kivetelt dob.", "nem dobott '"#exception_type"' kivetelt.")

/// Környezeti változóhoz hasonlít -- ilyen nincs a gtest-ben
#define EXPECT_ENVEQ(expected, actual) gtest_lite::EXPECTSTR(std::getenv(expected), actual, gtest_lite::eqstr, __FILE__, __LINE__, "EXPECT_ENVEQ(" #expected ", " #actual ")" )

/// Környezeti változóhoz hasonlít -- ilyen nincs a gtest-ben (kisbetű/nagybetű azonos)
#define EXPECT_ENVCASEEQ(expected, actual) gtest_lite::EXPECTSTR(std::getenv(expected), actual, gtest_lite::eqstrcase, __FILE__, __LINE__, "EXPECT_ENVCASEEQ(" #expected ", " #actual ")" )

#if __cplusplus >= 201103L
/// Reguláris kifejezés illesztése
# define EXPECT_REGEXP(expected, actual, match, err) gtest_lite::EXPECTREGEXP(expected, actual, match, err, __FILE__, __LINE__, "EXPECT_REGEXP(" #expected ", " #actual ", " #match ")" )
#endif
////--------------------------------------------------------------------------------------------
/// ASSERT típusú ellenőrzések. CSak 1-2 van megvalósítva. Nem ostream& -val térnek vissza !!!
/// Kivételt várunk

/// Azonosságot elváró makró
#define ASSERT_EQ(expected, actual) gtest_lite::ASSERT_(expected, actual, gtest_lite::eq, "ASSER_EQ")

/// Nem várunk kivételt
#define ASSERT_NO_THROW(statement) try { gtest_lite::test.tmp = true; statement; } \
    catch (...) { gtest_lite::test.tmp = false; }\
    ASSERTTHROW(statement, "nem dob kivetelt.", "kivetelt dobott.")


/// Segédmakró egy adattag, vagy tagfüggvény létezésének tesztelésére futási időben
/// Ötlet:
/// https://cpptalk.wordpress.com/2009/09/12/substitution-failure-is-not-an-error-2
/// Használat:
/// CREATE_Has_(size)
/// ... if (Has_size<std::string>::member)...
#define CREATE_Has_(X) \
template<typename T> struct _Has_##X {  \
    struct Fallback { int X; };         \
    struct Derived : T, Fallback {};    \
    template<typename C, C> struct ChT; \
    template<typename D> static char (&f(ChT<int Fallback::*, &D::X>*))[1]; \
    template<typename D> static char (&f(...))[2]; \
    static bool const member = sizeof(f<Derived>(0)) == 2; \
};

/// Segédfüggvény egy publikus adattag, vagy tagfüggvény létezésének tesztelésére
/// fordítási időben
inline void hasMember(...) {}

/// Segédsablon típuskonverzió futás közbeni ellenőrzésere
template <typename F, typename T>
struct _Is_Types {
    template<typename D> static char (&f(D))[1];
    template<typename D> static char (&f(...))[2];
    static bool const convertable = sizeof(f<T>(F())) == 1;
};

/// -----------------------------------
/// Belső megvalósításhoz tartozó makrók, és osztályok.
/// Nem célszerű közvetlenül használni, vagy módosítani
/// -----------------------------------

/// EXPECTTHROW: kivételkezelés
#define EXPECTTHROW(statement, exp, act) gtest_lite::test.expect(gtest_lite::test.tmp, __FILE__, __LINE__, #statement) \
    << "** Az utasitas " << (act) \
    << "\n** Azt vartuk, hogy " << (exp) << std::endl

#define ASSERTTHROW(statement, exp, act) gtest_lite::test.expect(gtest_lite::test.tmp, __FILE__, __LINE__, #statement) \
    << "** Az utasitas " << (act) \
    << "\n** Azt vartuk, hogy " << (exp) << std::endl; if (!gtest_lite::test.status) { gtest_lite::test.end(); break; }

#define ASSERT_(expected, actual, fn, op) EXPECT_(expected, actual, fn, __FILE__, __LINE__, #op "(" #expected ", " #actual ")" ); \
    if (!gtest_lite::test.status) { gtest_lite::test.end(); break; }

#ifdef CPORTA
#define GTINIT(is)  \
    int magic;      \
    is >> magic;
#else
#define GTINIT(IS)
#endif // CPORTA

#ifdef CPORTA
#define GTEND(os)  \
    os << magic << (gtest_lite::test.fail() ? " NO" : " OK?") << std::endl;
#else
#define GTEND(os)
#endif // CPORTA

/// gtest_lite: a keretrendszer függvényinek és objektumainak névtere
namespace gtest_lite {

/// Tesztek állapotát tároló osztály.
/// Egyetlen egy statikus példány keletkezik, aminek a
/// destruktora a futás végén hívódik meg.
struct Test {
    int sum;            ///< tesztek számlálója
    int failed;         ///< hibás tesztek
    int ablocks;        ///< allokált blokkok száma
    bool status;        ///< éppen futó teszt státusza
    bool tmp;           ///< temp a kivételkezeléshez;
    std::string name;   ///< éppen futó teszt neve
    std::fstream null;  ///< nyelő, ha nem kell kiírni semmit
    static Test& getTest() {
        static Test instance;///< egyedüli (singleton) példány
        return instance;
    }
private:    /// singleton minta miatt
    Test() :sum(0), failed(0), status(false), null("/dev/null") {}
    Test(const Test&);
    void operator=(const Test&);
public:
    /// Teszt kezdete
    void begin(const char *n) {
        name = n; status = true;
#ifdef MEMTRACE
        ablocks = memtrace::allocated_blocks();
#endif
#ifndef CPORTA
        std::cerr << "\n---> " << name << std::endl;
#endif // CPORTA
        ++sum;
    }
    /// Teszt vége
    std::ostream& end(bool memchk = false) {
#ifdef MEMTRACE
        if (memchk && ablocks != memtrace::allocated_blocks()) {
            status = false;
            return std::cerr << "** Lehet, hogy nem szabaditott fel minden memoriat! **" << std::endl;
        }
#endif
#ifdef CPORTA
        if (!status)
#endif // CPORTA
            std::cerr << (status ? "     SIKERES" : "** HIBAS ****") << "\t" << name << " <---" << std::endl;
        if (!status)
            return std::cerr;
        else
            return null;
    }

    bool fail() { return failed; }

    bool astatus() { return status; }

    /// Eredményt adminisztráló tagfüggvény True a jó eset.
    std::ostream& expect(bool st, const char *file, int line, const char *expr, bool pr = false) {
        if (!st) {
            ++failed;
            status = false;
        }
        if (!st || pr) {
            std::string str(file);
            size_t i = str.rfind("\\");
            if (i == std::string::npos) i = str.rfind("/");
            if (i == std::string::npos) i = 0; else i++;
            return std::cerr << "\n**** " << &file[i] << "(" << line << "): " << expr << " ****" << std::endl;
        }
        return null;
    }

    /// Destruktor
    ~Test() {
#ifdef CPORTA
        if (failed)
#endif // CPORTA
            std::cerr << "\n==== TESZT VEGE ==== HIBAS/OSSZES: " << failed << "/" << sum << std::endl;
    }
};

/// A statikus referencia minden fordítási egységben keletkezik, de
/// mindegyik egyetlen példányra fog hivatkozni a singleton minta miatt
static Test& test = Test::getTest();

/// általános sablon a várt értékhez.
template <typename T1, typename T2>
std::ostream& EXPECT_(T1 exp, T2 act, bool (*pred)(T1, T2), const char *file, int line,
                      const char *expr, const char *lhs = "elvart", const char *rhs = "aktual") {
    return test.expect(pred(exp, act), file, line, expr)
        << "** " << lhs << ": " << std::boolalpha << exp
        << "\n** " << rhs << ": " << std::boolalpha << act << std::endl;
}

/// pointerre specializált sablon a várt értékhez.
template <typename T1, typename T2>
std::ostream& EXPECT_(T1* exp, T2* act, bool (*pred)(T1*, T2*), const char *file, int line,
                      const char *expr, const char *lhs = "elvart", const char *rhs = "aktual") {
    return test.expect(pred(exp, act), file, line, expr)
        << "** " << lhs << ": " << (void*) exp
        << "\n** " << rhs << ": " << (void*) act << std::endl;
}

#if __cplusplus >= 201103L
/// nullptr-re specializált sablon a várt értékhez.
template <typename T1>
std::ostream& EXPECT_(T1* exp, std::nullptr_t act, bool (*pred)(T1*, std::nullptr_t), const char *file, int line,
                      const char *expr, const char *lhs = "elvart", const char *rhs = "aktual") {
    return test.expect(pred(exp, act), file, line, expr)
        << "** " << lhs << ": " << (void*) exp
        << "\n** " << rhs << ": " << (void*) act << std::endl;
}
#endif

/// stringek összehasonlításához.
/// azért nem spec. mert a sima EQ-ra másként kell működnie.
inline
std::ostream& EXPECTSTR(const char *exp, const char *act, bool (*pred)(const char*, const char*), const char *file, int line,
                      const char *expr, const char *lhs = "elvart", const char *rhs = "aktual") {
    return test.expect(pred(exp, act), file, line, expr)
        << "** " << lhs << ": " << (exp == NULL ? "NULL pointer" : std::string("\"") + exp + std::string("\""))
        << "\n** " << rhs << ": " << (act == NULL ? "NULL pointer" : std::string("\"") + act + std::string("\"")) << std::endl;
}

#if __cplusplus >= 201103L
/// regexp összehasonlításhoz.
template <typename E, typename S>
int count_regexp(E exp, S str) {
    std::regex rexp(exp);
    auto w_beg = std::sregex_iterator(str.begin(), str.end(), rexp);
    auto w_end = std::sregex_iterator();
    return std::distance(w_beg, w_end);
}

template <typename E, typename S>
std::ostream& EXPECTREGEXP(E exp, S str, int match, const char *err,  const char *file, int line,
                    const char *expr, const char *lhs = "regexp", const char *rhs = "string",
                    const char *m = "elvart/illeszkedik") {
    int cnt = count_regexp(exp, str);
    if (match < 0) match = cnt;
    return test.expect(cnt == match, file, line, expr)
        << "** " << lhs << ": " << std::string("\"") + exp + std::string("\"")
        << "\n** " << rhs << ": " << (err == NULL ? std::string("\"") + str + std::string("\"") : err)
        << "\n** " << m << ": " << match << "/" << cnt << std::endl;
}
#endif

/// segéd sablonok a relációkhoz.
/// azért nem STL (algorithm), mert csak a függvény lehet, hogy menjen a deduckció
template <typename T1, typename T2>
bool eq(T1 a, T2 b) { return a == b; }

inline
bool eqstr(const char *a, const char *b) {
    if (a != NULL && b != NULL)
        return strcmp(a, b) == 0;
    return false;
}

inline
bool eqstrcase(const char *a, const char *b) {
    if (a != NULL && b != NULL) {
        while (toupper(*a) == toupper(*b) && *a != '\0') {
            a++;
            b++;
        }
        return *a == *b;
    }
    return false;

}

template <typename T1, typename T2>
bool ne(T1 a, T2 b) { return a != b; }

inline
bool nestr(const char *a, const char *b) {
    if (a != NULL && b != NULL)
        return strcmp(a, b) != 0;
    return false;
}

template <typename T1, typename T2>
bool le(T1 a, T2 b) { return a <= b; }

template <typename T1, typename T2>
bool lt(T1 a, T2 b) { return a < b; }

template <typename T1, typename T2>
bool ge(T1 a, T2 b) { return a >= b; }

template <typename T1, typename T2>
bool gt(T1 a, T2 b) { return a > b; }

/// Segédsablon valós számok összehasonlításához
/// Nem bombabiztos, de nekünk most jó lesz
/// Elméleti hátér:
/// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
template <typename T>
bool almostEQ(T a, T  b) {
    // eps: ha a relatív, vagy abszolút hiba ettől kisebb, akkor elfogadjuk
    T eps = 10 * std::numeric_limits<T>::epsilon(); // 10-szer a legkisebb érték
    if (a == b) return true;
    if (fabs(a - b) < eps)
       return true;
    double aa = fabs(a);
    double ba = fabs(b);
    if (aa < ba) {
        aa = ba;
        ba = fabs(a);
    }
    return (aa - ba) < aa * eps;
}

} // namespace gtest_lite

#endif // GTEST_LITE_H