/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

    $Log$
    Revision 1.3  2005/11/29 12:39:42  marc
    make it compilable with gcc 4.0.2 and newer doxygen

    Revision 1.2  2005/04/07 20:42:38  marc
    renamed loggerhierarchy from mrw.gccfunctiontrace to mrw.fn

    Revision 1.1  2005/03/11 21:07:55  marc
    initial version


         1         2         3         4         5         6         7         8
    5678901234567890123456789012345678901234567890123456789012345678901234567890
*/

#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/helpers/properties.h>
#include <cppunit/TestFixture.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/XmlOutputter.h>
#include <fstream>
#include <mrw/file.hxx>
#include <mrw/regexp.hxx>
#include <mrw/string.hxx>
#include <mrw/stacktrace.hxx>
#include <map>

#if (__GNUC__==3 && __GNUC_MINOR__<4 || __GNUC__<3) \
  && defined(_REENTRANT) && !defined(_MT)
#define _MT
#endif

namespace HalloWelt {
  
  class A {
    public:
      void method() {
      }
  };

  void fn(int) {
    A().method();
    A().method();
  }

  void fn1() {
    fn(1);
    fn(2);
  }

}

void anotherFunction() {
  HalloWelt::fn1();
}

#ifdef _MT
#include <boost/thread/thread.hpp>
//#include <unistd.h> // sleep, the one from boost::thread does not work!
class Thread {
  public:
    void operator()() {
      anotherFunction();
    }
};
#endif

namespace mrw {
  class AutoFunctionTraceLog4CxxTest: public CppUnit::TestFixture {
    public:
      void testcase() {
        mrw::StackTrace::createSymtable();
#ifdef _MT
        try {mrw::File::remove("mrwautofunctiontracelog4cxx_test-mt.trace");}
#else
        try {mrw::File::remove("mrwautofunctiontracelog4cxx_test.trace");}
#endif
        catch (...) {}
        log4cxx::helpers::Properties properties;
        std::string name, cont;
        properties.setProperty((name="log4j.rootLogger",
                                std::string(name.begin(), name.end())),
                               (cont="OFF, A1",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn",
                                std::string(name.begin(), name.end())),
                               (cont="DEBUG",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.global",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.allocator",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.std.*",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.log4cxx",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.boost",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.Thread",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.mrw",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.std",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.new",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.CppUnit",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.logger.mrw.fn.__gnu_cxx",
                                std::string(name.begin(), name.end())),
                               (cont="OFF",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.appender.A1",
                                std::string(name.begin(), name.end())),
                               (cont="org.apache.log4j.FileAppender",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.appender.A1.layout",
                                std::string(name.begin(), name.end())),
                               (cont="org.apache.log4j.PatternLayout",
                                std::string(cont.begin(), cont.end())));
#ifdef _MT
        properties.setProperty((name="log4j.appender.A1.layout.ConversionPattern",
                                std::string(name.begin(), name.end())),
                               (cont="%t-%-27c%m%n",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.appender.A1.filename",
                                std::string(name.begin(), name.end())),
                               (cont="mrwautofunctiontracelog4cxx_test-mt.trace",
                                std::string(cont.begin(), cont.end())));
#else
        properties.setProperty((name="log4j.appender.A1.layout.ConversionPattern",
                                std::string(name.begin(), name.end())),
                               (cont="%-27c%m%n",
                                std::string(cont.begin(), cont.end())));
        properties.setProperty((name="log4j.appender.A1.filename",
                                std::string(name.begin(), name.end())),
                               (cont="mrwautofunctiontracelog4cxx_test.trace",
                                std::string(cont.begin(), cont.end())));
#endif
        log4cxx::PropertyConfigurator::configure(properties);
#ifdef _MT
        // sleep(4); // to be reproducable, wait for "main" flag
        Thread threadFunction;
        boost::thread thread1(threadFunction);
        boost::thread thread2(threadFunction);
        boost::thread thread3(threadFunction);
#endif
        anotherFunction();
#ifdef _MT
        thread1.join();
        thread2.join();
        thread3.join();
#endif
      }
      void checkfile() {
        mrw::RegExp match
          ("^mrw\\.fn\\.anotherFunction     ( ? ? ?)\\\\ anotherFunction\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.fn1       \\1 \\\\ HalloWelt::fn1\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.fn        \\1  \\\\ HalloWelt::fn\\(int\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   \\\\ HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   / HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   \\\\ HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   / HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.fn        \\1  / HalloWelt::fn\\(int\\)\n"
           "mrw\\.fn\\.HalloWelt\\.fn        \\1  \\\\ HalloWelt::fn\\(int\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   \\\\ HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   / HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   \\\\ HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.A\\.method  \\1   / HalloWelt::A::method\\(\\)\n"
           "mrw\\.fn\\.HalloWelt\\.fn        \\1  / HalloWelt::fn\\(int\\)\n"
           "mrw\\.fn\\.HalloWelt\\.fn1       \\1 / HalloWelt::fn1\\(\\)\n"
           "mrw\\.fn\\.anotherFunction     \\1/ anotherFunction\\(\\)\n$");
#ifdef _MT
        std::string log(mrw::File::read("mrwautofunctiontracelog4cxx_test-mt.trace"));
        typedef std::map<unsigned long, std::string> Logs;
        Logs logs;
        for (std::string::size_type pos(0), last(0);
             (pos=log.find('\n', pos+1))!=std::string::npos; last=pos) {
          std::string::size_type dash(log.find('-', last));
          CPPUNIT_ASSERT_MESSAGE("\"-\" not found",
                                 dash!=std::string::npos);
          logs[mrw::to<unsigned long>(log.substr(last, dash-last))] +=
            log.substr(dash+1, pos-dash);
        }
        CPPUNIT_ASSERT_EQUAL((Logs::size_type)4, logs.size()); // 4 threads
        for (Logs::iterator it(logs.begin()); it!=logs.end(); ++it)
          CPPUNIT_ASSERT_MESSAGE("The following text does not match the "
                                 "Expectation:\n--------------------\n"
                                 +it->second+"--------------------",
                                 match(it->second));
        mrw::File::remove("mrwautofunctiontracelog4cxx_test-mt.trace");
#else
        CPPUNIT_ASSERT_MESSAGE
          ("The following text does not match the "
           "Expectation:\n--------------------\n"
           +mrw::File::read("mrwautofunctiontracelog4cxx_test.trace")
           +"--------------------",
           match(mrw::File::read("mrwautofunctiontracelog4cxx_test.trace")));
        mrw::File::remove("mrwautofunctiontracelog4cxx_test.trace");
#endif
      }
      CPPUNIT_TEST_SUITE(AutoFunctionTraceLog4CxxTest);
      CPPUNIT_TEST(testcase);
      CPPUNIT_TEST(checkfile);
      CPPUNIT_TEST_SUITE_END();
  };
  CPPUNIT_TEST_SUITE_REGISTRATION(AutoFunctionTraceLog4CxxTest);
}

int main(int argc, char** argv) try {
  std::ofstream ofs((*argv+std::string(".xml")).c_str());
  CppUnit::TextUi::TestRunner runner;
  runner.setOutputter(new CppUnit::XmlOutputter(&runner.result(), ofs));
  runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
  return runner.run() ? 0 : 1;
 } catch (std::exception& e) {
  std::cerr<<"***Exception: "<<e.what()<<std::endl;
  return 1;
 }
