text:
/*
 *  This file is part of Poedit (https://poedit.net)
 *
 *  Copyright (C) 2013-2022 Vaclav Slavik
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 *
 */

#include "language.h"

#include <cctype>
#include <algorithm>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <regex>

#include <boost/algorithm/string.hpp>

#include <unicode/locid.h>
#include <unicode/coll.h>
#include <unicode/utypes.h>

#include <wx/filename.h>

#include "str_helpers.h"
#include "pluralforms/pl_evaluate.h"

#ifdef HAVE_CLD2
    #ifdef HAVE_CLD2_PUBLIC_COMPACT_LANG_DET_H
        #include <cld2/public/compact_lang_det.h>
        #include <cld2/public/encodings.h>
    #else
        #include "public/compact_lang_det.h"
        #include "public/encodings.h"
    #endif
#endif

namespace
{

// see http://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html
// for description of permitted formats
const std::wregex RE_LANG_CODE(L"([a-z]){2,3}(_([A-Z]{2}|[0-9]{3}))?(@[a-z]+)?");

// a more permissive variant of the same that TryNormalize() would fix
const std::wregex RE_LANG_CODE_PERMISSIVE(L"([a-zA-Z]){2,3}([_-]([a-zA-Z]{2}|[0-9]{3}))?(@[a-zA-Z]+)?");

// try some normalizations: s/-/_/, case adjustments
void TryNormalize(std::wstring& s)
{
    auto begin = s.begin();
    auto end = s.end();

    size_t pos = s.rfind('@');
    if (pos != std::wstring::npos)
    {
        for (auto x = begin + pos; x != end; ++x)
            *x = std::tolower(*x);
        end = begin + pos;
    }

    bool upper = false;
    for (auto x = begin; x != end; ++x)
    {
        if (*x == '-')
            *x = '_';
        if (*x == '_')
            upper = true;
        else if (std::isupper(*x) && !upper)
            *x = std::tolower(*x);
        else if (std::islower(*x) && upper)
            *x = std::toupper(*x);
    }
}

bool IsISOLanguage(const std::string& s)
{
    const char *test = s.c_str();
    for (const char * const* i = icu::Locale::getISOLanguages(); *i != nullptr; ++i)
    {
        if (strcmp(test, *i) == 0)
            return true;
    }
    return false;
}

bool IsISOCountry(const std::string& s)
{
    const char *test = s.c_str();
    for (const char * const* i = icu::Locale::getISOCountries(); *i != nullptr; ++i)
    {
        if (strcmp(test, *i) == 0)
            return true;
    }
    return false;
}

// Mapping of names to their respective ISO codes.
struct DisplayNamesData
{
    typedef std::unordered_map<std::wstring, std::string> Map;
    Map names, namesEng;
    std::vector<std::wstring> sortedNames;
};

std::once_flag of_namesList;

const DisplayNamesData& GetDisplayNamesData()
{
    static DisplayNamesData data;

    std::call_once(of_namesList, [=]{
        auto locEng = icu::Locale::getEnglish();
        std::vector<icu::UnicodeString> names;

        int32_t count;
        const icu::Locale *loc = icu::Locale::getAvailableLocales(count);
        names.reserve(count);
        for (int i = 0; i < count; i++, loc++)
        {
            auto language = loc->getLanguage();
            auto script = loc->getScript();
            auto country = loc->getCountry();
            auto variant = loc->getVariant();

            // TODO: for now, ignore variants here and in FormatForRoundtrip(),
            //       because translating them between gettext and ICU is nontrivial
            if (variant != nullptr && *variant != '\0')
                continue;

            icu::UnicodeString s;
            loc->getDisplayName(s);
            names.push_back(s);

            if (strcmp(language, "zh") == 0 && *country == '\0')
            {
                if (strcmp(script, "Hans") == 0)
                    country = "CN";
                else if (strcmp(script, "Hant") == 0)
                    country = "TW";
            }

            std::string code(language);
            if (*country != '\0')
            {
                code += '_';
                code += country;
            }
            if (*script != '\0')
            {
                if (strcmp(script, "Latn") == 0)
                {
                    code += "@latin";
                }
                else if (strcmp(script, "Cyrl") == 0)
                {
                    // add @cyrillic only if it's not the default already
                    if (strcmp(language, "sr") != 0)
                        code += "@cyrillic";
                }
            }

            s.foldCase();
            data.names[str::to_wstring(s)] = code;

            loc->getDisplayName(locEng, s);
            s.foldCase();
            data.namesEng[str::to_wstring(s)] = code;
        }

        // sort the names alphabetically for data.sortedNames:
        UErrorCode err = U_ZERO_ERROR;
        std::unique_ptr<icu::Collator> coll(icu::Collator::createInstance(err));
        if (coll)
        {
            coll->setStrength(icu::Collator::SECONDARY); // case insensitive

            std::sort(names.begin(), names.end(),
                      [&coll](const icu::UnicodeString& a, const icu::UnicodeString& b){
                          UErrorCode e = U_ZERO_ERROR;
                          return coll->compare(a, b, e) == UCOL_LESS;
                      });
        }
        else
        {
            std::sort(names.begin(), names.end());
        }

        // convert into std::wstring
        data.sortedNames.reserve(names.size());
        for (auto s: names)
            data.sortedNames.push_back(str::to_wstring(s));
    });

    return data;
}

std::string DoGetLanguageTag(const Language& lang)
{
    if (lang.Code() == "zh_CN")
         return "zh-Hans";
    else if (lang.Code() == "zh_TW")
         return "zh-Hant";

    auto l = lang.Lang();
    auto c = lang.Country();
    auto v = lang.Variant();

    std::string tag = l;

    if (v == "latin")
    {
        tag += "-Latn";
        v.clear();
    }
    else if (v == "cyrillic")
    {
        tag += "-Cyrl";
        v.clear();
    }

    if (!c.empty())
        tag += "-" + c;

    if (!v.empty())
    {
        // Encode variant that wasn't special-handled as a private use subtag, see
        // https://tools.ietf.org/html/rfc5646#section-2.2.7 (e.g. "de-DE-x-formal")
        tag += "-x-" + v;
    }

    return tag;
}

inline bool DoIsRTL(const Language& lang)
{
    auto locale = lang.IcuLocaleName();
    return uloc_isRightToLeft(locale.c_str());
}

} // anonymous namespace

void Language::Init(const std::string& code)
{
    m_code = code;

    if (IsValid())
    {
        m_tag = DoGetLanguageTag(*this);
        m_icuLocale = m_tag;
        m_direction = DoIsRTL(*this) ? TextDirection::RTL : TextDirection::LTR;
    }
    else
    {
        m_tag.clear();
        m_icuLocale.clear();
        m_direction = TextDirection::LTR;
    }
}

bool Language::IsValidCode(const std::wstring& s)
{
    return std::regex_match(s, RE_LANG_CODE);
}

std::string Language::Lang() const
{
    return m_code.substr(0, m_code.find_first_of("_@"));
}

std::string Language::Country() const
{
    const size_t pos = m_code.find('_');
    if (pos == std::string::npos)
        return std::string();

    const size_t endpos = m_code.rfind('@');
    if (endpos == std::string::npos)
        return m_code.substr(pos+1);
    else
        return m_code.substr(pos+1, endpos - (pos+1));
}

std::string Language::LangAndCountry() const
{
    return m_code.substr(0, m_code.rfind('@'));
}

std::string Language::Variant() const
{
    const size_t pos = m_code.rfind('@');
    if (pos == std::string::npos)
        return std::string();
    else
        return m_code.substr(pos + 1);
}

Language Language::TryParse(const std::wstring& s)
{
    if (IsValidCode(s))
        return Language(s);

    if (s == "zh-Hans")
        return Language("zh_CN");
    else if (s == "zh-Hant")
        return Language("zh_TW");

    // Is it a standard language code?
    if (std::regex_match(s, RE_LANG_CODE_PERMISSIVE))
    {
        std::wstring s2(s);
        TryNormalize(s2);
        if (IsValidCode(s2))
            return Language(s2);
    }

    // If not, perhaps it's a human-readable name (perhaps coming from the language control)?
    auto names = GetDisplayNamesData();
    icu::UnicodeString s_icu = str::to_icu(s);
    s_icu.foldCase();
    std::wstring folded = str::to_wstring(s_icu);
    auto i = names.names.find(folded);
    if (i != names.names.end())
        return Language(i->second);

    // Maybe it was in English?
    i = names.namesEng.find(folded);
    if (i != names.namesEng.end())
        return Language(i->second);

    // Maybe it was a BCP 47 language tag?
    auto fromTag = FromLanguageTag(str::to_utf8(s));
    if (fromTag.IsValid())
        return fromTag;

    return Language(); // invalid
}

Language Language::TryParseWithValidation(const std::wstring& s)
{
    Language lang = Language::TryParse(s);
    if (!lang.IsValid())
        return Language(); // invalid

    if (!IsISOLanguage(lang.Lang()))
        return Language(); // invalid

    auto country = lang.Country();
    if (!country.empty() && !IsISOCountry(country))
        return Language(); // invalid

    return lang;
}

Language Language::FromLanguageTag(const std::string& tag)
{
    char locale[512];
    UErrorCode status = U_ZERO_ERROR;
    uloc_forLanguageTag(tag.c_str(), locale, 512, NULL, &status);
    if (U_FAILURE(status))
        return Language();

    Language lang;
    lang.m_tag = tag;
    lang.m_icuLocale = locale;

    char buf[512];
    if (uloc_getLanguage(locale, buf, 512, &status))
        lang.m_code = buf;
    if (uloc_getCountry(locale, buf, 512, &status))
        lang.m_code += "_" + std::string(buf);

    // ICU converts private use subtag into 'x' keyword, e.g. de-DE-x-formal => de_DE@x=formal
    static const std::regex re_private_subtag("@x=([^@]+)$");
    std::cmatch m;
    if (std::regex_search(locale, m, re_private_subtag))
        lang.m_code += "@" + m.str(1);

    lang.m_direction = DoIsRTL(lang) ? TextDirection::RTL : TextDirection::LTR;

    return lang;
}

Language Language::FromLegacyNames(const std::string& lang, const std::string& country)
{
    if (lang.empty())
        return Language(); // invalid

    #include "language_impl_legacy.h"

    std::string code;
    auto i = isoLanguages.find(lang);
    if ( i != isoLanguages.end() )
        code = i->second;
    else
        return Language(); // invalid

    if (!country.empty())
    {
        auto iC = isoCountries.find(country);
        if ( iC != isoCountries.end() )
            code += "_" + iC->second;
    }

    return Language(code);
}

PluralFormsExpr Language::DefaultPluralFormsExpr() const
{
    if (!IsValid())
        return PluralFormsExpr();

    static const std::unordered_map<std::string, PluralFormsExpr> forms = {
        #include "language_impl_plurals.h"
    };

    auto i = forms.find(m_code);
    if ( i != forms.end() )
        return i->second;

    i = forms.find(LangAndCountry());
    if ( i != forms.end() )
        return i->second;

    i = forms.find(Lang());
    if ( i != forms.end() )
        return i->second;

    return PluralFormsExpr();
}

int Language::nplurals() const
{
    return DefaultPluralFormsExpr().nplurals();
}

icu::Locale Language::ToIcu() const
{
    if (!IsValid())
        return icu::Locale::getEnglish();

    return icu::Locale(IcuLocaleName().c_str());
}

wxString Language::DisplayName() const
{
    icu::UnicodeString s;
    ToIcu().getDisplayName(s);
    return str::to_wx(s);
}

wxString Language::LanguageDisplayName() const
{
    icu::UnicodeString s;
    ToIcu().getDisplayLanguage(s);
    return str::to_wx(s);
}

wxString Language::DisplayNameInItself() const
{
    auto loc = ToIcu();
    icu::UnicodeString s;
    loc.getDisplayName(loc, s);
    return str::to_wx(s);
}

wxString Language::FormatForRoundtrip() const
{
    // Can't show all variants nicely, but some common one can be
    auto v = Variant();
    if (!v.empty() && v != "latin" && v != "cyrillic")
        return m_code;

    wxString disp = DisplayName();
    // ICU isn't 100% reliable, some of the display names it produces
    // (e.g. "Chinese (China)" aren't in the list of known locale names
    // (here because zh-Trans is preferred to zh_CN). So make sure it can
    // be parsed back first.
    if (TryParse(disp.ToStdWstring()).IsValid())
        return disp;
    else
        return m_code;
}

const std::vector<std::wstring>& Language::AllFormattedNames()
{
    return GetDisplayNamesData().sortedNames;
}

Language Language::TryGuessFromFilename(const wxString& filename)
{
    wxFileName fn(filename);
    fn.MakeAbsolute();

    // Try matching the filename first:
    //  - entire name
    //  - suffix (foo.cs_CZ.po, wordpressTheme-cs_CZ.po)
    //  - directory name (cs_CZ, cs.lproj, cs/LC_MESSAGES)
    std::wstring name = fn.GetName().ToStdWstring();
    Language lang = Language::TryParseWithValidation(name);
            if (lang.IsValid())
                return lang;

    size_t pos = name.find_first_of(L".-_");
    while (pos != wxString::npos)
    {
        auto part = name.substr(pos+1);
        lang = Language::TryParseWithValidation(part);
        if (lang.IsValid())
            return lang;
         pos = name.find_first_of(L".-_",  pos+1);
    }

    auto dirs = fn.GetDirs();
    if (!dirs.empty())
    {
        auto d = dirs.rbegin();
        if (d->IsSameAs("LC_MESSAGES", /*caseSensitive=*/false))
        {
            if (++d == dirs.rend())
                return Language(); // failed to match
        }
        wxString rest;
        if (d->EndsWith(".lproj", &rest))
            return Language::TryParseWithValidation(rest.ToStdWstring());
        else
            return Language::TryParseWithValidation(d->ToStdWstring());
    }

    return Language(); // failed to match
}

Language Language::TryDetectFromText(const char *buffer, size_t len, Language probableLanguage)
{
#ifdef HAVE_CLD2
    using namespace CLD2;

    CLDHints hints = {NULL, NULL, UNKNOWN_ENCODING, UNKNOWN_LANGUAGE};
    if (probableLanguage.IsValid())
    {
        if (probableLanguage.Lang() == "en")
            hints.language_hint = ENGLISH;
        else
            hints.language_hint = GetLanguageFromName(probableLanguage.LanguageTag().c_str());
    }

    // three best guesses; we don't care, but they must be passed in
    CLD2::Language language3[3];
    int percent3[3];
    double normalized_score3[3];
    // more result info:
    int text_bytes;
    bool is_reliable;

    auto lang = CLD2::ExtDetectLanguageSummary(
                        buffer, (int)len,
                        /*is_plain_text=*/false,
                        &hints,
                        /*flags=*/0,
                        language3, percent3, normalized_score3,
                        /*resultchunkvector=*/nullptr,
                        &text_bytes,
                        &is_reliable);

    if (!is_reliable &&
        language3[0] == lang &&
        language3[1] == UNKNOWN_LANGUAGE && language3[2] == UNKNOWN_LANGUAGE &&
        percent3[1] == 0 && percent3[2] == 0)
    {
        // supposedly unreliable, but no other alternatives detected, so use it
        is_reliable = true;
    }

    if (lang == UNKNOWN_LANGUAGE || !is_reliable)
        return Language();

    // CLD2 penalizes English in bilingual content in some cases as "boilerplate"
    // because it is tailored for the web. So e.g. 66% English, 33% Italian is
    // tagged as Italian.
    //
    // Poedit's bias is the opposite: English is almost always the correct answer
    // for PO source language. Fix this up manually.
    if (lang != language3[0] && language3[0] == CLD2::ENGLISH && language3[1] == lang)
        lang = language3[0];

    return Language::TryParse(LanguageCode(lang));
#else
    (void)buffer;
    (void)len;
    return probableLanguage;
#endif
}

PluralFormsExpr::PluralFormsExpr() : m_calcCreated(true)
{
}

PluralFormsExpr::PluralFormsExpr(const std::string& expr, int nplurals)
    : m_expr(expr), m_nplurals(nplurals), m_calcCreated(false)
{
}

PluralFormsExpr::~PluralFormsExpr()
{
}

int PluralFormsExpr::nplurals() const
{
    if (m_nplurals != -1)
        return m_nplurals;
    if (m_calc)
        return m_calc->nplurals();

    const std::regex re("^nplurals=([0-9]+)");
    std::smatch m;
    if (std::regex_match(m_expr, m, re))
        return std::stoi(m.str(1));
    else
        return -1;
}

std::shared_ptr<PluralFormsCalculator> PluralFormsExpr::calc() const
{
    auto self = const_cast<PluralFormsExpr*>(this);
    if (m_calcCreated)
        return m_calc;
    if (!m_expr.empty())
        self->m_calc = PluralFormsCalculator::make(m_expr.c_str());
    self->m_calcCreated = true;
    return m_calc;
}

bool PluralFormsExpr::operator==(const PluralFormsExpr& other) const
{
    if (m_expr == other.m_expr)
        return true;

    // do some normalization to avoid unnecessary complains when the only
    // differences are in whitespace for example:
    auto expr1 = boost::erase_all_copy(m_expr, " \t");
    auto expr2 = boost::erase_all_copy(other.m_expr, " \t");
    if (expr1 == expr2)
        return true;

    // failing that, compare the expressions semantically:
    auto calc1 = calc();
    auto calc2 = other.calc();

    if (!calc1 || !calc2)
        return false; // at least one is invalid _and_ the strings are different due to code above

    if (calc1->nplurals() != calc2->nplurals())
        return false;

    for (int i = 0; i < MAX_EXAMPLES_COUNT; i++)
    {
        if (calc1->evaluate(i) != calc2->evaluate(i))
            return false;
    }
    // both expressions are identical on all tested integers
    return true;
}

int PluralFormsExpr::evaluate_for_n(int n) const
{
    auto c = calc();
    return c ? c->evaluate(n) : 0;
}

ext: cpp


text:
//  (C) Copyright Gennadiy Rozental 2001-2008.
//  Distributed under the Boost Software License, Version 1.0.
//  (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)

//  See http://www.boost.org/libs/test for the library home page.
//
//  File        : $RCSfile$
//
//  Version     : $Revision$
//
//  Description : defines test_unit, test_case, test_case_results, test_suite and test_tree_visitor
// ***************************************************************************

#ifndef BOOST_TEST_UNIT_TEST_SUITE_IMPL_HPP_071894GER
#define BOOST_TEST_UNIT_TEST_SUITE_IMPL_HPP_071894GER

// Boost.Test
#include <boost/test/detail/config.hpp>
#include <boost/test/detail/global_typedef.hpp>
#include <boost/test/utils/class_properties.hpp>
#include <boost/test/utils/callback.hpp>
#include <boost/test/detail/fwd_decl.hpp>
#include <boost/test/detail/workaround.hpp>
#include <boost/test/test_observer.hpp>

// Boost
#include <boost/shared_ptr.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/type.hpp>
#include <boost/type_traits/is_const.hpp>

// STL
#include <typeinfo> // for typeid
#include <string>   // for std::string
#include <list>     // for std::list
#include <vector>   // for std::vector

#include <boost/test/detail/suppress_warnings.hpp>

//____________________________________________________________________________//

namespace boost {

namespace unit_test {

// ************************************************************************** //
// **************                   test_unit                  ************** //
// ************************************************************************** //

class BOOST_TEST_DECL test_unit {
public:
    enum { type = tut_any };

    // Constructor
    test_unit( const_string tu_name, test_unit_type t );

    // dependencies management
    void    depends_on( test_unit* tu );
    bool    check_dependencies() const;

    // Public r/o properties
    typedef BOOST_READONLY_PROPERTY(test_unit_id,(framework_impl))  id_t;
    typedef BOOST_READONLY_PROPERTY(test_unit_id,(test_suite))      parent_id_t;
    readonly_property<test_unit_type>   p_type;                 // type for this test unit
    readonly_property<const_string>     p_type_name;            // "case"/"suite"
    id_t                                p_id;                   // unique id for this test unit
    parent_id_t                         p_parent_id;            // parent test suite id

    // Public r/w properties
    readwrite_property<std::string>     p_name;                 // name for this test unit
    readwrite_property<unsigned>        p_timeout;              // timeout for the test unit execution
    readwrite_property<counter_t>       p_expected_failures;    // number of expected failures in this test unit
    mutable readwrite_property<bool>    p_enabled;              // enabled status for this unit

    void                                increase_exp_fail( unsigned num );

protected:
    ~test_unit();

private:
    // Data members
    std::list<test_unit_id>             m_dependencies;
};

// ************************************************************************** //
// **************              test_case_generator             ************** //
// ************************************************************************** //

class BOOST_TEST_DECL test_unit_generator {
public:
    virtual test_unit*  next() const = 0;

protected:
    BOOST_TEST_PROTECTED_VIRTUAL ~test_unit_generator() {}
};

// ************************************************************************** //
// **************                   test_case                  ************** //
// ************************************************************************** //

class BOOST_TEST_DECL test_case : public test_unit {
public:
    enum { type = tut_case };

    // Constructor
    test_case( const_string tc_name, callback0<> const& test_func );

    // Access methods
    callback0<> const&  test_func() const { return m_test_func; }

private:
    friend class framework_impl;
    ~test_case() {}

    // BOOST_MSVC <= 1200 have problems with callback as property
    // Data members
    callback0<> m_test_func;
};

// ************************************************************************** //
// **************                  test_suite                  ************** //
// ************************************************************************** //

class BOOST_TEST_DECL test_suite : public test_unit {
public:
    enum { type = tut_suite };

    // Constructor
    explicit        test_suite( const_string ts_name );

    // test unit list management
    void            add( test_unit* tu, counter_t expected_failures = 0, unsigned timeout = 0 );
    void            add( test_unit_generator const& gen, unsigned timeout = 0 );
    void            remove( test_unit_id id );

    // access methods
    test_unit_id    get( const_string tu_name ) const;
    std::size_t     size() const { return m_members.size(); }

protected:
    friend BOOST_TEST_DECL
    void        traverse_test_tree( test_suite const&, test_tree_visitor& );
    friend class framework_impl;
    virtual     ~test_suite() {}

    // Data members
    std::vector<test_unit_id> m_members;
};

// ************************************************************************** //
// **************               master_test_suite              ************** //
// ************************************************************************** //

class BOOST_TEST_DECL master_test_suite_t : public test_suite {
public:
    master_test_suite_t() : test_suite( "Master Test Suite" )
    , argc( 0 )
    , argv( 0 )
    {}

    // Data members
    int      argc;
    char**   argv;
};

// ************************************************************************** //
// **************               test_tree_visitor              ************** //
// ************************************************************************** //

class BOOST_TEST_DECL test_tree_visitor {
public:
    // test tree visitor interface
    virtual void    visit( test_case const& )               {}
    virtual bool    test_suite_start( test_suite const& )   { return true; }
    virtual void    test_suite_finish( test_suite const& )  {}

protected:
    BOOST_TEST_PROTECTED_VIRTUAL ~test_tree_visitor() {}
};

// ************************************************************************** //
// **************               traverse_test_tree             ************** //
// ************************************************************************** //

BOOST_TEST_DECL void    traverse_test_tree( test_case const&, test_tree_visitor& );
BOOST_TEST_DECL void    traverse_test_tree( test_suite const&, test_tree_visitor& );
BOOST_TEST_DECL void    traverse_test_tree( test_unit_id     , test_tree_visitor& );

//____________________________________________________________________________//

inline void
traverse_test_tree( test_unit const& tu, test_tree_visitor& V )
{
    if( tu.p_type == tut_case )
        traverse_test_tree( static_cast<test_case const&>( tu ), V );
    else
        traverse_test_tree( static_cast<test_suite const&>( tu ), V );
}

//____________________________________________________________________________//

// ************************************************************************** //
// **************                test_case_counter             ************** //
// ************************************************************************** //

class test_case_counter : public test_tree_visitor {
public:
    // Constructor
    test_case_counter() : p_count( 0 ) {}

    BOOST_READONLY_PROPERTY( counter_t, (test_case_counter)) p_count;
private:
    // test tree visitor interface
    virtual void    visit( test_case const& );
    virtual bool    test_suite_start( test_suite const& ts )    { return ts.p_enabled; }
};

// ************************************************************************** //
// **************               test_being_aborted             ************** //
// ************************************************************************** //

struct BOOST_TEST_DECL test_being_aborted {};

// ************************************************************************** //
// **************               object generators              ************** //
// ************************************************************************** //

namespace ut_detail {

BOOST_TEST_DECL std::string normalize_test_case_name( const_string tu_name );

template<typename InstanceType,typename UserTestCase>
struct user_tc_method_invoker {
    typedef void (UserTestCase::*TestMethod )();

    user_tc_method_invoker( shared_ptr<InstanceType> inst, TestMethod test_method )
    : m_inst( inst ), m_test_method( test_method ) {}

    void operator()() { ((*m_inst).*m_test_method)(); }

    shared_ptr<InstanceType> m_inst;
    TestMethod               m_test_method;
};

} // namespace ut_detail

//____________________________________________________________________________//

inline test_case*
make_test_case( callback0<> const& test_func, const_string tc_name )
{
    return new test_case( ut_detail::normalize_test_case_name( tc_name ), test_func );
}

//____________________________________________________________________________//

template<typename UserTestCase, typename InstanceType>
inline test_case*
make_test_case( void (UserTestCase::*           test_method )(),
                const_string                    tc_name,
                boost::shared_ptr<InstanceType> user_test_case )
{
    return new test_case( ut_detail::normalize_test_case_name( tc_name ),
                          ut_detail::user_tc_method_invoker<InstanceType,UserTestCase>( user_test_case, test_method ) );
}

//____________________________________________________________________________//

// ************************************************************************** //
// **************           auto_test_unit_registrar           ************** //
// ************************************************************************** //

namespace ut_detail {

struct BOOST_TEST_DECL auto_test_unit_registrar
{
    // Constructors
                auto_test_unit_registrar( test_case* tc, counter_t exp_fail );
    explicit    auto_test_unit_registrar( const_string ts_name );
    explicit    auto_test_unit_registrar( test_unit_generator const& tc_gen );
    explicit    auto_test_unit_registrar( int );

private:
    static std::list<test_suite*>& curr_ts_store();
};

//____________________________________________________________________________//

template<typename T>
struct auto_tc_exp_fail {
    auto_tc_exp_fail() : m_value( 0 ) {}

    explicit    auto_tc_exp_fail( unsigned v )
    : m_value( v )
    {
        instance() = this;
    }

    static auto_tc_exp_fail*& instance()
    {
        static auto_tc_exp_fail     inst;
        static auto_tc_exp_fail*    inst_ptr = &inst;

        return inst_ptr;
    }

    unsigned    value() const { return m_value; }

private:
    // Data members
    unsigned    m_value;
};

//____________________________________________________________________________//

} // namespace ut_detail

// ************************************************************************** //
// **************                global_fixture                ************** //
// ************************************************************************** //

class BOOST_TEST_DECL global_fixture : public test_observer {
public:
    // Constructor
    global_fixture();
};

//____________________________________________________________________________//

namespace ut_detail {

template<typename F>
struct global_fixture_impl : public global_fixture {
    // Constructor
    global_fixture_impl(): m_fixure( 0 )    {}

    // test observer interface
    virtual void    test_start( counter_t ) { m_fixure = new F; }
    virtual void    test_finish()           { delete m_fixure; m_fixure = 0; }
    virtual void    test_aborted()          { delete m_fixure; m_fixure = 0; }

private:
    // Data members
    F*  m_fixure;
};

// ************************************************************************** //
// **************          test_case_template_invoker          ************** //
// ************************************************************************** //

template<typename TestCaseTemplate,typename TestType>
class test_case_template_invoker {
public:
    void    operator()()    { TestCaseTemplate::run( (boost::type<TestType>*)0 ); }
};

// ************************************************************************** //
// **************           generate_test_case_4_type          ************** //
// ************************************************************************** //

template<typename Generator,typename TestCaseTemplate>
struct generate_test_case_4_type {
    explicit    generate_test_case_4_type( const_string tc_name, Generator& G )
    : m_test_case_name( tc_name )
    , m_holder( G )
    {}

    template<typename TestType>
    void        operator()( mpl::identity<TestType> )
    {
        std::string full_name;
        assign_op( full_name, m_test_case_name, 0 );
        full_name += '<';
        full_name += typeid(TestType).name();
        if( boost::is_const<TestType>::value )
            full_name += " const";
        full_name += '>';

        m_holder.m_test_cases.push_back(
            new test_case( full_name, test_case_template_invoker<TestCaseTemplate,TestType>() ) );
    }

private:
    // Data members
    const_string    m_test_case_name;
    Generator&      m_holder;
};

// ************************************************************************** //
// **************              test_case_template              ************** //
// ************************************************************************** //

template<typename TestCaseTemplate,typename TestTypesList>
class template_test_case_gen : public test_unit_generator {
public:
    // Constructor
    template_test_case_gen( const_string tc_name )
    {
        typedef generate_test_case_4_type<template_test_case_gen<TestCaseTemplate,TestTypesList>,
                                          TestCaseTemplate
        > single_test_gen;
        mpl::for_each<TestTypesList,mpl::make_identity<mpl::_> >( single_test_gen( tc_name, *this ) );
    }

    virtual test_unit* next() const
    {
        if( m_test_cases.empty() )
            return 0;

        test_unit* res = m_test_cases.front();
        m_test_cases.pop_front();

        return res;
    }

    // Data members
    mutable std::list<test_unit*> m_test_cases;
};

//____________________________________________________________________________//

} // namespace ut_detail

} // unit_test

} // namespace boost

#include <boost/test/detail/enable_warnings.hpp>

#endif // BOOST_TEST_UNIT_TEST_SUITE_IMPL_HPP_071894GER

ext: hpp

max_stars_repo_name: rudylee/expo

max_issues_repo_name: rudylee/expo

max_forks_repo_name: rudylee/expo