/*
  This file is part of Valgrind, a dynamic binary instrumentation
  framework.

  Copyright (C) 2008-2008 Google Inc
     opensource@google.com

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 2 of the
  License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  02111-1307, USA.

  The GNU General Public License is contained in the file COPYING.
*/

/* Author: Konstantin Serebryany <opensource@google.com>

 This file contains a simple test suite for some of our old unit-tests.
 These tests are likely to be moved to googletest framework over time.
*/

#include <algorithm>
#include <gtest/gtest.h>
#include <string>
#include <ostream>

#include "old_test_suite.h"

Mutex printf_mu;
std::map<int, Test> *TheMapOfTests = NULL;
std::vector<int> tests_to_run;
std::set<int> tests_to_exclude;

#ifndef MAIN_INIT_ACTION
#define MAIN_INIT_ACTION
#endif

int ParseInt(const char *str) {
  int ret = 0;
  const char *cur = str;
  do {
    if (!isdigit(*cur)) {
      printf("\"%s\" is not a valid number\n", str);
      exit(1);
    }

    ret = ret*10 + (*cur - '0');
  } while (*(++cur));
  return ret;
}

class RandomGenerator {
 public:
  RandomGenerator(int seed) { srand(seed); }
  size_t operator( )(size_t n) const { return rand() % n; }
};

TEST(NonGtestTests, All) {
  for (size_t i = 0; i < tests_to_run.size(); i++) {
    int test_id = tests_to_run[i];
    if (tests_to_exclude.count(test_id) > 0) {
      printf("test%i was excluded\n", test_id);
    } else {
      (*TheMapOfTests)[test_id].Run();
      ANNOTATE_FLUSH_EXPECTED_RACES();
    }
  }
}

#ifndef ANDROID // GTest version is too old.
class PerformanceTestEventListener: public ::testing::EmptyTestEventListener {
 public:
  virtual void OnTestEnd(const ::testing::TestInfo& test_info) {
    if (strcmp(test_info.test_case_name(), "StressTests") == 0 ||
        strcmp(test_info.test_case_name(), "PerformanceTests") == 0) {
      const ::testing::TestResult* result = test_info.result();
      times_[test_info.name()].push_back(result->elapsed_time());
    }
  }

  virtual void OnTestProgramEnd(const ::testing::UnitTest& unit_test) {
    for (std::map<string, std::vector<long long int> >::iterator it = times_.begin();
         it != times_.end(); ++it) {
      printf("*RESULT %s: time= %s ms\n", it->first.c_str(), join_str(it->second).c_str());
    }
  }

 private:
  std::map<string, std::vector<long long int> > times_;

  string join_str(std::vector<long long int> values) {
    bool first = true;
    bool single = (values.size() == 1);
    std::ostringstream st;
    if (!single) {
      st << "[";
    }
    for (std::vector<long long int>::iterator it = values.begin();
         it != values.end(); ++it) {
      if (first) {
        first = false;
      } else {
        st << " ";
      }
      st << *it;
    }
    if (!single) {
      st << "]";
    }
    return st.str();
  }
};
#endif

int main(int argc, char** argv) {
  MAIN_INIT_ACTION;
  testing::InitGoogleTest(&argc, argv);
  printf("FLAGS [phb=%i, rv=%i]\n", Tsan_PureHappensBefore(),
      Tsan_RaceVerifier());

  int shuffle_seed = 0;  // non-zero to shuffle.

  int id = 1;
  while (id < argc) {
    char *cur_arg = argv[id];
    if (!strcmp(cur_arg, "benchmark")) {
      for (std::map<int,Test>::iterator it = TheMapOfTests->begin();
        it != TheMapOfTests->end(); ++it) {
        if(it->second.flags_ & PERFORMANCE)
          tests_to_run.push_back(it->first);
      }
    } else if (!strcmp(cur_arg, "demo")) {
      for (std::map<int,Test>::iterator it = TheMapOfTests->begin();
        it != TheMapOfTests->end();  ++it) {
        if(it->second.flags_ & RACE_DEMO)
          tests_to_run.push_back(it->first);
      }
    } else if (!strncmp(cur_arg, "shuffle", 7)) {
      if (strlen(cur_arg) == 7) {
        shuffle_seed = GetTimeInMs();
        printf("Shuffling with seed = %i\n", shuffle_seed);
      } else {
        CHECK(cur_arg[7] == '=');
        shuffle_seed = ParseInt(cur_arg + 8);
      }
    } else {
      if (isdigit(cur_arg[0])) {
        // Enqueue the test specified.
        int test_id = ParseInt(cur_arg);
        if (!TheMapOfTests->count(test_id)) {
          printf("Unknown test id: %d\n", test_id);
          exit(1);
        }
        tests_to_run.push_back(test_id);
      } else if (cur_arg[0] == '-') {
        // Exclude the test specified.
        int test_id = ParseInt(cur_arg + 1);
        if (!TheMapOfTests->count(test_id)) {
          printf("Unknown test id: %d\n", test_id);
          exit(1);
        }
        tests_to_exclude.insert(test_id);
      } else {
        printf("Unknown argument: %s\n", cur_arg);
        exit(1);
      }
    }

    id++;
  }

  if (tests_to_run.size() == 0) {
    printf("No tests specified.\nRunning default set of tests...\n");
    for (std::map<int,Test>::iterator it = TheMapOfTests->begin();
        it != TheMapOfTests->end();
        ++it) {
      if(it->second.flags_ & EXCLUDE_FROM_ALL) continue;
      if(it->second.flags_ & RACE_DEMO) continue;
      tests_to_run.push_back(it->first);
    }
  }

  if (shuffle_seed > 0) {
    RandomGenerator rnd(shuffle_seed);
    random_shuffle(tests_to_run.begin(), tests_to_run.end(), rnd);
  }

#ifndef ANDROID // GTest version is too old.
  ::testing::TestEventListeners& listeners =
        ::testing::UnitTest::GetInstance()->listeners();
  // Adds a listener to the end.  Google Test takes the ownership.
  listeners.Append(new PerformanceTestEventListener());
#endif

  return RUN_ALL_TESTS();
}
// End {{{1
 // vim:shiftwidth=2:softtabstop=2:expandtab:foldmethod=marker