<commit_before>#include "ROOTUnitTestSupport.h"

#include "TClass.h"
#include "TInterpreter.h"
#include "TROOT.h"
#include "TSystem.h"

#include <sstream>
#include <string>
#include <vector>

// FIXME: We should probably have such a facility in TCling.
static void cleanup()
{
   // Remove AutoDict
   void* dir = gSystem->OpenDirectory(gSystem->pwd());
   const char* name = 0;
   while ((name = gSystem->GetDirEntry(dir)))
      if (!strncmp(name, "AutoDict_", 9))
         gSystem->Unlink(name);

   gSystem->FreeDirectory(dir);
}

class TClingTests : public ::testing::Test {
protected:
   // virtual void SetUp() { }

   // FIXME: We cannot rely on TearDown because it is executed at the end of
   // every test. This triggers another bug in the dictionary generation phase,
   // possibly due to concurrent file system operations.
   //virtual void TearDown() {
      // If there are failures we want to keep the created files.
      //if (!::testing::Test::HasFatalFailure())
      //   cleanup();
   //}

};

// FIXME: Merge with TearDown.
struct CleanupRAII {
   CleanupRAII() {
      if (!::testing::Test::HasFatalFailure())
         cleanup();
   }
} Cleanup;

TEST_F(TClingTests, GenerateDictionaryErrorHandling)
{
   // Check error reporting and handling.
   ROOT_EXPECT_ERROR(EXPECT_FALSE(gInterpreter->GenerateDictionary("", "")), "TInterpreter::TCling::GenerateDictionary",
                     "Cannot generate dictionary without passing classes.");
   ROOT_EXPECT_ERROR(EXPECT_FALSE(gInterpreter->GenerateDictionary(nullptr, nullptr)),
                     "TInterpreter::TCling::GenerateDictionary", "Cannot generate dictionary without passing classes.");
}

TEST_F(TClingTests, GenerateDictionaryRegression)
{
   // Make sure we do not crash or go in an infinite loop.
   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set<int>"));
   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set<int>", ""));
   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set<int>", "set"));

   // FIXME: This makes the linkdef parser go in an infinite loop.
   //EXPECT_TRUE(gInterpreter->GenerateDictionary("std::vector<std::array<int, 5>>", ""));
}

#if !defined(_MSC_VER) || defined(R__ENABLE_BROKEN_WIN_TESTS)
TEST_F(TClingTests, GenerateDictionary)
{
   auto cl = TClass::GetClass("vector<TNamed*>");
   EXPECT_FALSE(cl && cl->IsLoaded());

   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::vector<TNamed*>"));
   cl = TClass::GetClass("vector<TNamed*>");
   EXPECT_TRUE(cl != nullptr);
   EXPECT_TRUE(cl->IsLoaded());
}
#endif

// Test ROOT-6967
TEST_F(TClingTests, GetEnumWithSameVariableName)
{
   gInterpreter->ProcessLine("int en;enum en{kNone};");
   auto en = gInterpreter->GetEnum(nullptr, "en");
   EXPECT_TRUE(en != nullptr);
}

// Check if we can get the source code of function definitions.
TEST_F(TClingTests, MakeInterpreterValue)
{
   gInterpreter->Declare("void my_func_to_print() {}");
   std::unique_ptr<TInterpreterValue> v = gInterpreter->MakeInterpreterValue();
   gInterpreter->Evaluate("my_func_to_print", *v);
   EXPECT_THAT(v->ToString(), testing::HasSubstr("void my_func_to_print"));
}

static std::string MakeLibNamePlatformIndependent(const std::string &libName)
{
   // Return an empty string if input is not a library name.
   // Sometimes, libName can be the binary name (i.e. TClingTest, for this test)
   if (libName.empty() || libName.compare(0, 3, "lib") != 0)
      return {};

   // Return library name without lib prefix and extension.
   return libName.substr(3, libName.find('.') - 3);
}

// Check if the heavily used interface in TCling::AutoLoad returns consistent
// results.
TEST_F(TClingTests, GetClassSharedLibs)
{
   // Shortens the invocation.
   auto GetLibs = [](const char *cls) -> std::string {
      if (const char *val = gInterpreter->GetClassSharedLibs(cls))
         return val;
      return "";
   };

   std::string lib = GetLibs("TLorentzVector");
   EXPECT_EQ("Physics", MakeLibNamePlatformIndependent(lib));

   // FIXME: This should return GenVector. The default args of the LorentzVector
   // are shadowed by Vector4Dfwd.h.
   lib = GetLibs("ROOT::Math::LorentzVector");
   EXPECT_EQ("", MakeLibNamePlatformIndependent(lib));

   lib = GetLibs("ROOT::Math::PxPyPzE4D<float>");
   EXPECT_EQ("GenVector", MakeLibNamePlatformIndependent(lib));

   // FIXME: We should probably resolve again to GenVector as it contains the
   // template pattern.
   lib = GetLibs("ROOT::Math::PxPyPzE4D<int>");
   EXPECT_EQ("", MakeLibNamePlatformIndependent(lib));

   lib = GetLibs("vector<ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<double> > >");
   EXPECT_EQ("GenVector", MakeLibNamePlatformIndependent(lib));

   lib = GetLibs("ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<double> > ");
#ifdef R__USE_CXXMODULES
   EXPECT_EQ("GenVector", MakeLibNamePlatformIndependent(lib));
#else
   // FIXME: This is another bug in the non-modules functionality. Note the
   // trailing space...
   EXPECT_EQ("", MakeLibNamePlatformIndependent(lib));
#endif

   // FIXME: Another bug in non-modules:
   // GetLibs("ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<float> >")
   //    != GetLibs("ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<float>>")
   // note the missing space.
}

static std::string MakeDepLibsPlatformIndependent(const std::string &libs) {
   auto trim = [](const std::string &s) {
      std::string ret = s;
      while (!ret.empty() && std::isspace(ret[0]))
         ret.erase(0, 1);
      while (!ret.empty() && std::isspace(ret[ret.size() - 1]))
         ret.erase(ret.size() - 1, 1);
      return ret;
   };

   auto split = [](const std::string &s) -> std::vector<std::string> {
      std::vector<std::string> ret;
      std::istringstream istr(s);
      std::string part;
      while (std::getline(istr, part, ' '))
         ret.push_back(part);
      return ret;
   };

   std::vector<std::string> splitLibs = split(trim(libs));
   assert(!splitLibs.empty());

   std::sort(splitLibs.begin() + 1, splitLibs.end());
   std::transform(splitLibs.begin(), splitLibs.end(), splitLibs.begin(), MakeLibNamePlatformIndependent);

   std::string result;
   for (std::string lib : splitLibs)
      if (!lib.empty())
         result += lib + ' ';

   return trim(result);
}

#if !defined(_MSC_VER) || defined(R__ENABLE_BROKEN_WIN_TESTS)
// Check the interface computing the dependencies of a given library.
TEST_F(TClingTests, GetSharedLibDeps)
{
   // Shortens the invocation.
   auto GetLibDeps = [](const char *lib) -> const char* {
      return gInterpreter->GetSharedLibDeps(lib, /*tryDyld*/true);
   };

   std::string SeenDeps
      = MakeDepLibsPlatformIndependent(GetLibDeps("libGenVector.so"));
#ifdef R__MACOSX
   // It may depend on tbb
   EXPECT_EQ(SeenDeps.substr(0, 9), "GenVector");
#else
    // Depends only on libCore.so but libCore.so is loaded and thus missing.
    EXPECT_STREQ("GenVector", SeenDeps.c_str());
#endif

   SeenDeps = MakeDepLibsPlatformIndependent(GetLibDeps("libTreePlayer.so"));
   std::string SeenDepsRef = SeenDeps;

   // Depending on the configuration we expect:
   // TreePlayer Gpad Graf Graf3d Hist [Imt] [MathCore] MultiProc Net Tree [tbb]..
   // FIXME: We should add a generic gtest regex matcher and use a regex here.
   EXPECT_EQ(SeenDepsRef.compare(0, 32, "TreePlayer Gpad Graf Graf3d Hist"), 0);
   EXPECT_NE(SeenDepsRef.find("MultiProc Net Tree"), std::string::npos);

   ROOT_EXPECT_ERROR(EXPECT_TRUE(nullptr == GetLibDeps("")), "TCling__GetSharedLibImmediateDepsSlow",
                     "Cannot find library ''");
   ROOT_EXPECT_ERROR(EXPECT_TRUE(nullptr == GetLibDeps("   ")), "TCling__GetSharedLibImmediateDepsSlow",
                     "Cannot find library '   '");
}
#endif

// Check the interface which interacts with the cling::LookupHelper.
TEST_F(TClingTests, ClingLookupHelper) {
  // Exception spec evaluation.
  // Emulate the LookupHelper sequence:
  // auto S = LookupHelper::findScope("ROOT::Internal::RDF", diag)
  // LookupHelper::findAnyFunction(S, "RDataFrameTake<float>", diag)
  // LookupHelper::findAnyFunction(S, "RDataFrameTake<std::vector<float>>", diag)
  auto *cl = gCling->ClassInfo_Factory("ROOT::Internal::RDF");
  gCling->GetFunction(cl, "RDataFrameTake<float>");
  gCling->GetFunction(cl, "RDataFrameTake<std::vector<float>>");
}

// Check that compiled and interpreted statics share the same address.
TEST_F(TClingTests, ROOT10499) {
#if !defined(_MSC_VER) || defined(R__ENABLE_BROKEN_WIN_TESTS)
   EXPECT_EQ((void*)&std::cout, (void*)gInterpreter->Calc("&std::cout"));
   EXPECT_EQ((void*)&std::cerr, (void*)gInterpreter->Calc("&std::cerr"));
   // strangely enough, this works on the command prompt, but not in this test...
   EXPECT_EQ((void*)&errno, (void*)gInterpreter->Calc("&errno"));
#endif
}
<commit_msg>Include what you use in TClingTests<commit_after>#include "ROOTUnitTestSupport.h"

#include "TClass.h"
#include "TInterpreter.h"
#include "TROOT.h"
#include "TSystem.h"

#include "gmock/gmock.h"

#include <sstream>
#include <string>
#include <vector>

// FIXME: We should probably have such a facility in TCling.
static void cleanup()
{
   // Remove AutoDict
   void* dir = gSystem->OpenDirectory(gSystem->pwd());
   const char* name = 0;
   while ((name = gSystem->GetDirEntry(dir)))
      if (!strncmp(name, "AutoDict_", 9))
         gSystem->Unlink(name);

   gSystem->FreeDirectory(dir);
}

class TClingTests : public ::testing::Test {
protected:
   // virtual void SetUp() { }

   // FIXME: We cannot rely on TearDown because it is executed at the end of
   // every test. This triggers another bug in the dictionary generation phase,
   // possibly due to concurrent file system operations.
   //virtual void TearDown() {
      // If there are failures we want to keep the created files.
      //if (!::testing::Test::HasFatalFailure())
      //   cleanup();
   //}

};

// FIXME: Merge with TearDown.
struct CleanupRAII {
   CleanupRAII() {
      if (!::testing::Test::HasFatalFailure())
         cleanup();
   }
} Cleanup;

TEST_F(TClingTests, GenerateDictionaryErrorHandling)
{
   // Check error reporting and handling.
   ROOT_EXPECT_ERROR(EXPECT_FALSE(gInterpreter->GenerateDictionary("", "")), "TInterpreter::TCling::GenerateDictionary",
                     "Cannot generate dictionary without passing classes.");
   ROOT_EXPECT_ERROR(EXPECT_FALSE(gInterpreter->GenerateDictionary(nullptr, nullptr)),
                     "TInterpreter::TCling::GenerateDictionary", "Cannot generate dictionary without passing classes.");
}

TEST_F(TClingTests, GenerateDictionaryRegression)
{
   // Make sure we do not crash or go in an infinite loop.
   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set<int>"));
   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set<int>", ""));
   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set<int>", "set"));

   // FIXME: This makes the linkdef parser go in an infinite loop.
   //EXPECT_TRUE(gInterpreter->GenerateDictionary("std::vector<std::array<int, 5>>", ""));
}

#if !defined(_MSC_VER) || defined(R__ENABLE_BROKEN_WIN_TESTS)
TEST_F(TClingTests, GenerateDictionary)
{
   auto cl = TClass::GetClass("vector<TNamed*>");
   EXPECT_FALSE(cl && cl->IsLoaded());

   EXPECT_TRUE(gInterpreter->GenerateDictionary("std::vector<TNamed*>"));
   cl = TClass::GetClass("vector<TNamed*>");
   EXPECT_TRUE(cl != nullptr);
   EXPECT_TRUE(cl->IsLoaded());
}
#endif

// Test ROOT-6967
TEST_F(TClingTests, GetEnumWithSameVariableName)
{
   gInterpreter->ProcessLine("int en;enum en{kNone};");
   auto en = gInterpreter->GetEnum(nullptr, "en");
   EXPECT_TRUE(en != nullptr);
}

// Check if we can get the source code of function definitions.
TEST_F(TClingTests, MakeInterpreterValue)
{
   gInterpreter->Declare("void my_func_to_print() {}");
   std::unique_ptr<TInterpreterValue> v = gInterpreter->MakeInterpreterValue();
   gInterpreter->Evaluate("my_func_to_print", *v);
   EXPECT_THAT(v->ToString(), testing::HasSubstr("void my_func_to_print"));
}

static std::string MakeLibNamePlatformIndependent(const std::string &libName)
{
   // Return an empty string if input is not a library name.
   // Sometimes, libName can be the binary name (i.e. TClingTest, for this test)
   if (libName.empty() || libName.compare(0, 3, "lib") != 0)
      return {};

   // Return library name without lib prefix and extension.
   return libName.substr(3, libName.find('.') - 3);
}

// Check if the heavily used interface in TCling::AutoLoad returns consistent
// results.
TEST_F(TClingTests, GetClassSharedLibs)
{
   // Shortens the invocation.
   auto GetLibs = [](const char *cls) -> std::string {
      if (const char *val = gInterpreter->GetClassSharedLibs(cls))
         return val;
      return "";
   };

   std::string lib = GetLibs("TLorentzVector");
   EXPECT_EQ("Physics", MakeLibNamePlatformIndependent(lib));

   // FIXME: This should return GenVector. The default args of the LorentzVector
   // are shadowed by Vector4Dfwd.h.
   lib = GetLibs("ROOT::Math::LorentzVector");
   EXPECT_EQ("", MakeLibNamePlatformIndependent(lib));

   lib = GetLibs("ROOT::Math::PxPyPzE4D<float>");
   EXPECT_EQ("GenVector", MakeLibNamePlatformIndependent(lib));

   // FIXME: We should probably resolve again to GenVector as it contains the
   // template pattern.
   lib = GetLibs("ROOT::Math::PxPyPzE4D<int>");
   EXPECT_EQ("", MakeLibNamePlatformIndependent(lib));

   lib = GetLibs("vector<ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<double> > >");
   EXPECT_EQ("GenVector", MakeLibNamePlatformIndependent(lib));

   lib = GetLibs("ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<double> > ");
#ifdef R__USE_CXXMODULES
   EXPECT_EQ("GenVector", MakeLibNamePlatformIndependent(lib));
#else
   // FIXME: This is another bug in the non-modules functionality. Note the
   // trailing space...
   EXPECT_EQ("", MakeLibNamePlatformIndependent(lib));
#endif

   // FIXME: Another bug in non-modules:
   // GetLibs("ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<float> >")
   //    != GetLibs("ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<float>>")
   // note the missing space.
}

static std::string MakeDepLibsPlatformIndependent(const std::string &libs) {
   auto trim = [](const std::string &s) {
      std::string ret = s;
      while (!ret.empty() && std::isspace(ret[0]))
         ret.erase(0, 1);
      while (!ret.empty() && std::isspace(ret[ret.size() - 1]))
         ret.erase(ret.size() - 1, 1);
      return ret;
   };

   auto split = [](const std::string &s) -> std::vector<std::string> {
      std::vector<std::string> ret;
      std::istringstream istr(s);
      std::string part;
      while (std::getline(istr, part, ' '))
         ret.push_back(part);
      return ret;
   };

   std::vector<std::string> splitLibs = split(trim(libs));
   assert(!splitLibs.empty());

   std::sort(splitLibs.begin() + 1, splitLibs.end());
   std::transform(splitLibs.begin(), splitLibs.end(), splitLibs.begin(), MakeLibNamePlatformIndependent);

   std::string result;
   for (std::string lib : splitLibs)
      if (!lib.empty())
         result += lib + ' ';

   return trim(result);
}

#if !defined(_MSC_VER) || defined(R__ENABLE_BROKEN_WIN_TESTS)
// Check the interface computing the dependencies of a given library.
TEST_F(TClingTests, GetSharedLibDeps)
{
   // Shortens the invocation.
   auto GetLibDeps = [](const char *lib) -> const char* {
      return gInterpreter->GetSharedLibDeps(lib, /*tryDyld*/true);
   };

   std::string SeenDeps
      = MakeDepLibsPlatformIndependent(GetLibDeps("libGenVector.so"));
#ifdef R__MACOSX
   // It may depend on tbb
   EXPECT_EQ(SeenDeps.substr(0, 9), "GenVector");
#else
    // Depends only on libCore.so but libCore.so is loaded and thus missing.
    EXPECT_STREQ("GenVector", SeenDeps.c_str());
#endif

   SeenDeps = MakeDepLibsPlatformIndependent(GetLibDeps("libTreePlayer.so"));
   std::string SeenDepsRef = SeenDeps;

   // Depending on the configuration we expect:
   // TreePlayer Gpad Graf Graf3d Hist [Imt] [MathCore] MultiProc Net Tree [tbb]..
   // FIXME: We should add a generic gtest regex matcher and use a regex here.
   EXPECT_EQ(SeenDepsRef.compare(0, 32, "TreePlayer Gpad Graf Graf3d Hist"), 0);
   EXPECT_NE(SeenDepsRef.find("MultiProc Net Tree"), std::string::npos);

   ROOT_EXPECT_ERROR(EXPECT_TRUE(nullptr == GetLibDeps("")), "TCling__GetSharedLibImmediateDepsSlow",
                     "Cannot find library ''");
   ROOT_EXPECT_ERROR(EXPECT_TRUE(nullptr == GetLibDeps("   ")), "TCling__GetSharedLibImmediateDepsSlow",
                     "Cannot find library '   '");
}
#endif

// Check the interface which interacts with the cling::LookupHelper.
TEST_F(TClingTests, ClingLookupHelper) {
  // Exception spec evaluation.
  // Emulate the LookupHelper sequence:
  // auto S = LookupHelper::findScope("ROOT::Internal::RDF", diag)
  // LookupHelper::findAnyFunction(S, "RDataFrameTake<float>", diag)
  // LookupHelper::findAnyFunction(S, "RDataFrameTake<std::vector<float>>", diag)
  auto *cl = gCling->ClassInfo_Factory("ROOT::Internal::RDF");
  gCling->GetFunction(cl, "RDataFrameTake<float>");
  gCling->GetFunction(cl, "RDataFrameTake<std::vector<float>>");
}

// Check that compiled and interpreted statics share the same address.
TEST_F(TClingTests, ROOT10499) {
#if !defined(_MSC_VER) || defined(R__ENABLE_BROKEN_WIN_TESTS)
   EXPECT_EQ((void*)&std::cout, (void*)gInterpreter->Calc("&std::cout"));
   EXPECT_EQ((void*)&std::cerr, (void*)gInterpreter->Calc("&std::cerr"));
   // strangely enough, this works on the command prompt, but not in this test...
   EXPECT_EQ((void*)&errno, (void*)gInterpreter->Calc("&errno"));
#endif
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/strings/string_split.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/page_cycler/page_cycler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "googleurl/src/gurl.h"

// TODO(kbr): remove: http://crbug.com/222296
#if defined(OS_MACOSX)
#import "base/mac/mac_util.h"
#endif

// Basic PageCyclerBrowserTest structure; used in testing most of PageCycler's
// functionality.
class PageCyclerBrowserTest : public content::NotificationObserver,
                              public InProcessBrowserTest {
 public:
  PageCyclerBrowserTest() : page_cycler_(NULL) {
  }

  virtual ~PageCyclerBrowserTest() {
  }

  // Initialize file paths within a temporary directory; this should be
  // empty and nonexistent.
  virtual void InitFilePaths(base::FilePath temp_path) {
    temp_path_ = temp_path;
    urls_file_ = temp_path.AppendASCII("urls_file");
    errors_file_ = temp_path.AppendASCII("errors");
    stats_file_ = temp_path.AppendASCII("stats");

    ASSERT_FALSE(file_util::PathExists(urls_file_));
    ASSERT_FALSE(file_util::PathExists(errors_file_));
    ASSERT_FALSE(file_util::PathExists(stats_file_));
  }

  // Initialize a PageCycler using either the base fields, or using provided
  // ones.
  void InitPageCycler() {
    page_cycler_ = new PageCycler(browser(), urls_file());
    page_cycler_->set_errors_file(errors_file());
    page_cycler_->set_stats_file(stats_file());
  }

  void InitPageCycler(base::FilePath urls_file,
                      base::FilePath errors_file,
                      base::FilePath stats_file) {
    page_cycler_ = new PageCycler(browser(), urls_file);
    page_cycler_->set_errors_file(errors_file);
    page_cycler_->set_stats_file(stats_file);
  }

  void RegisterForNotifications() {
    registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED,
                   content::NotificationService::AllSources());
  }

  // Get a collection of basic urls which are stored in the test directory.
  // NOTE: |test_server| must be started first!
  std::vector<GURL> GetURLs() {
    std::vector<GURL> urls;
    urls.push_back(test_server()->GetURL("files/page_cycler/basic_html.html"));
    urls.push_back(test_server()->GetURL("files/page_cycler/basic_js.html"));
    urls.push_back(test_server()->GetURL("files/page_cycler/basic_css.html"));
    return urls;
  }

  // Read the errors file, and generate a vector of error strings.
  std::vector<std::string> GetErrorsFromFile() {
    std::string error_file_contents;
    CHECK(file_util::ReadFileToString(errors_file_,
                                      &error_file_contents));
    if (error_file_contents[error_file_contents.size() - 1] == '\n')
      error_file_contents.resize(error_file_contents.size() - 1);

    std::vector<std::string> errors;
    base::SplitString(error_file_contents, '\n', &errors);

    return errors;
  }

  // Convert a vector of GURLs into a newline-separated string, ready to be
  // written to the urls file for PageCycler to use.
  std::string GetStringFromURLs(std::vector<GURL> urls) {
    std::string urls_string;
    for (std::vector<GURL>::const_iterator iter = urls.begin();
         iter != urls.end(); ++iter)
      urls_string.append(iter->spec() + "\n");
    return urls_string;
  }

  // content::NotificationObserver.
  virtual void Observe(int type,
                       const content::NotificationSource& source,
                       const content::NotificationDetails& details) OVERRIDE {
    switch (type) {
      case chrome::NOTIFICATION_BROWSER_CLOSED:
        base::MessageLoop::current()->PostTask(
            FROM_HERE, base::MessageLoop::QuitClosure());
        break;
      default:
        NOTREACHED();
        break;
    }
  }

  base::FilePath urls_file() { return urls_file_; }
  base::FilePath errors_file() { return errors_file_; }
  base::FilePath stats_file() { return stats_file_; }
  PageCycler* page_cycler() { return page_cycler_; }

 protected:
  base::FilePath temp_path_;
  base::FilePath urls_file_;
  base::FilePath errors_file_;
  base::FilePath stats_file_;
  PageCycler* page_cycler_;
  content::NotificationRegistrar registrar_;
};

// Structure used for testing PageCycler's ability to playback a series of
// URLs given a cache directory.
class PageCyclerCachedBrowserTest : public PageCyclerBrowserTest {
 public:
  // For a cached test, we use the provided user data directory from the test
  // directory.
  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    base::FilePath test_dir;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
    test_dir = test_dir.AppendASCII("page_cycler");

    base::FilePath source_data_dir = test_dir.AppendASCII("cached_data_dir");
    CHECK(file_util::PathExists(source_data_dir));

    CHECK(user_data_dir_.CreateUniqueTempDir());

    base::FilePath dest_data_dir =
        user_data_dir_.path().AppendASCII("cached_data_dir");
    CHECK(!file_util::PathExists(dest_data_dir));

    CHECK(file_util::CopyDirectory(source_data_dir,
                                   user_data_dir_.path(),
                                   true));  // recursive.
    CHECK(file_util::PathExists(dest_data_dir));

    command_line->AppendSwitchPath(switches::kUserDataDir,
                                   dest_data_dir);
    command_line->AppendSwitch(switches::kPlaybackMode);
  }

  // Initialize the file paths to use the UserDataDir's urls file, instead
  // of one to be written.
  virtual void InitFilePaths(base::FilePath temp_path) OVERRIDE {
    urls_file_ = user_data_dir_.path().AppendASCII("cached_data_dir")
                                      .AppendASCII("urls");
    errors_file_ = temp_path.AppendASCII("errors");
    stats_file_ = temp_path.AppendASCII("stats");

    ASSERT_TRUE(file_util::PathExists(urls_file_));
    ASSERT_FALSE(file_util::PathExists(errors_file_));
    ASSERT_FALSE(file_util::PathExists(stats_file_));
  }

 private:
  // The directory storing the copy of the UserDataDir.
  base::ScopedTempDir user_data_dir_;
};

// Sanity check; iterate through a series of URLs and make sure there are no
// errors.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, BasicTest) {
  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::string urls_string = GetStringFromURLs(GetURLs());;

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_FALSE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));
}

// Test to make sure that PageCycler will recognize unvisitable URLs, and will
// handle them appropriately.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, UnvisitableURL) {
  const size_t kNumErrors = 1;
  const char kFakeURL[] = "http://www.pleasenoonehavethisurlanytimeinthenext"
                          "century.com/gibberish";
  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::vector<GURL> urls = GetURLs();
  urls.push_back(GURL(kFakeURL));
  std::string urls_string = GetStringFromURLs(urls);

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();

  ASSERT_EQ(kNumErrors, errors.size());

  // Check that each error message contains the fake URL (i.e., that it wasn't
  // from a valid URL, and that the fake URL was caught each time).
  ASSERT_NE(std::string::npos, errors[0].find(kFakeURL));
}

// Test that PageCycler will remove an invalid URL prior to running.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, InvalidURL) {
  const char kBadURL[] = "notarealurl";

  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::string urls_string = GetStringFromURLs(GetURLs());
  urls_string.append(kBadURL).append("\n");

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();
  ASSERT_EQ(1u, errors.size());

  std::string expected_error = "Omitting invalid URL: ";
  expected_error.append(kBadURL).append(".");

  ASSERT_FALSE(errors[0].compare(expected_error));
}

// Test that PageCycler will remove a Chrome Error URL prior to running.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, ChromeErrorURL) {
  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::vector<GURL> urls = GetURLs();
  urls.push_back(GURL(content::kUnreachableWebDataURL));
  std::string urls_string = GetStringFromURLs(urls);

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();
  ASSERT_EQ(1u, errors.size());

  std::string expected_error = "Chrome error pages are not allowed as urls. "
                               "Omitting url: ";
  expected_error.append(content::kUnreachableWebDataURL).append(".");

  ASSERT_FALSE(errors[0].compare(expected_error));
}

#if !defined(OS_CHROMEOS)
// TODO(rdevlin.cronin): Perhaps page cycler isn't completely implemented on
// ChromeOS?

// Test that PageCycler will visit all the urls from a cache directory
// successfully while in playback mode.
#if defined(OS_LINUX)
// Bug 159026: Fails on Linux in both debug and release mode.
#define MAYBE_PlaybackMode DISABLED_PlaybackMode
#elif (defined(OS_WIN) || defined(OS_MACOSX) ) && !defined(NDEBUG)
// Bug 131333: This test fails on a XP debug bot since Build 17609.
#define MAYBE_PlaybackMode DISABLED_PlaybackMode
#else
#define MAYBE_PlaybackMode PlaybackMode
#endif
IN_PROC_BROWSER_TEST_F(PageCyclerCachedBrowserTest, MAYBE_PlaybackMode) {
#if defined(OS_MACOSX)
  // TODO(kbr): re-enable: http://crbug.com/222296
  if (base::mac::IsOSMountainLionOrLater())
    return;
#endif

  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  InitPageCycler();

  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(stats_file()));
  ASSERT_FALSE(file_util::PathExists(errors_file()));
}
#endif  // !defined(OS_CHROMEOS)

#if !defined(OS_CHROMEOS)
// TODO(rdevlin.cronin): Perhaps page cycler isn't completely implemented on
// ChromeOS?

// Test that PageCycler will have a cache miss if a URL is missing from the
// cache directory while in playback mode.
// Bug 131333: This test fails on a XP debug bot since Build 17609.
#if (defined(OS_WIN) || defined(OS_MACOSX)) && !defined(NDEBUG)
#define MAYBE_URLNotInCache DISABLED_URLNotInCache
#else
#define MAYBE_URLNotInCache URLNotInCache
#endif
IN_PROC_BROWSER_TEST_F(PageCyclerCachedBrowserTest, MAYBE_URLNotInCache) {
  const char kCacheMissURL[] = "http://www.images.google.com/";

  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  // Only use a single URL that is not in cache. That's sufficient for the test
  // scenario, and makes things faster than needlessly cycling through all the
  // other URLs.

  base::FilePath new_urls_file = temp.path().AppendASCII("urls");
  ASSERT_FALSE(file_util::PathExists(new_urls_file));

  ASSERT_TRUE(file_util::WriteFile(new_urls_file, kCacheMissURL,
                                   sizeof(kCacheMissURL)));

  InitPageCycler(new_urls_file, errors_file(), stats_file());
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();
  ASSERT_EQ(1u, errors.size());

  std::string expected_error;
  expected_error.append("Failed to load the page at: ")
      .append(kCacheMissURL)
      .append(": The requested entry was not found in the cache.");

  ASSERT_FALSE(errors[0].compare(expected_error));
}
#endif  // !defined(OS_CHROMEOS)
<commit_msg>Disable flaky PageCyclerCachedBrowserTest.PlaybackMode<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/strings/string_split.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/page_cycler/page_cycler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "googleurl/src/gurl.h"

// TODO(kbr): remove: http://crbug.com/222296
#if defined(OS_MACOSX)
#import "base/mac/mac_util.h"
#endif

// Basic PageCyclerBrowserTest structure; used in testing most of PageCycler's
// functionality.
class PageCyclerBrowserTest : public content::NotificationObserver,
                              public InProcessBrowserTest {
 public:
  PageCyclerBrowserTest() : page_cycler_(NULL) {
  }

  virtual ~PageCyclerBrowserTest() {
  }

  // Initialize file paths within a temporary directory; this should be
  // empty and nonexistent.
  virtual void InitFilePaths(base::FilePath temp_path) {
    temp_path_ = temp_path;
    urls_file_ = temp_path.AppendASCII("urls_file");
    errors_file_ = temp_path.AppendASCII("errors");
    stats_file_ = temp_path.AppendASCII("stats");

    ASSERT_FALSE(file_util::PathExists(urls_file_));
    ASSERT_FALSE(file_util::PathExists(errors_file_));
    ASSERT_FALSE(file_util::PathExists(stats_file_));
  }

  // Initialize a PageCycler using either the base fields, or using provided
  // ones.
  void InitPageCycler() {
    page_cycler_ = new PageCycler(browser(), urls_file());
    page_cycler_->set_errors_file(errors_file());
    page_cycler_->set_stats_file(stats_file());
  }

  void InitPageCycler(base::FilePath urls_file,
                      base::FilePath errors_file,
                      base::FilePath stats_file) {
    page_cycler_ = new PageCycler(browser(), urls_file);
    page_cycler_->set_errors_file(errors_file);
    page_cycler_->set_stats_file(stats_file);
  }

  void RegisterForNotifications() {
    registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED,
                   content::NotificationService::AllSources());
  }

  // Get a collection of basic urls which are stored in the test directory.
  // NOTE: |test_server| must be started first!
  std::vector<GURL> GetURLs() {
    std::vector<GURL> urls;
    urls.push_back(test_server()->GetURL("files/page_cycler/basic_html.html"));
    urls.push_back(test_server()->GetURL("files/page_cycler/basic_js.html"));
    urls.push_back(test_server()->GetURL("files/page_cycler/basic_css.html"));
    return urls;
  }

  // Read the errors file, and generate a vector of error strings.
  std::vector<std::string> GetErrorsFromFile() {
    std::string error_file_contents;
    CHECK(file_util::ReadFileToString(errors_file_,
                                      &error_file_contents));
    if (error_file_contents[error_file_contents.size() - 1] == '\n')
      error_file_contents.resize(error_file_contents.size() - 1);

    std::vector<std::string> errors;
    base::SplitString(error_file_contents, '\n', &errors);

    return errors;
  }

  // Convert a vector of GURLs into a newline-separated string, ready to be
  // written to the urls file for PageCycler to use.
  std::string GetStringFromURLs(std::vector<GURL> urls) {
    std::string urls_string;
    for (std::vector<GURL>::const_iterator iter = urls.begin();
         iter != urls.end(); ++iter)
      urls_string.append(iter->spec() + "\n");
    return urls_string;
  }

  // content::NotificationObserver.
  virtual void Observe(int type,
                       const content::NotificationSource& source,
                       const content::NotificationDetails& details) OVERRIDE {
    switch (type) {
      case chrome::NOTIFICATION_BROWSER_CLOSED:
        base::MessageLoop::current()->PostTask(
            FROM_HERE, base::MessageLoop::QuitClosure());
        break;
      default:
        NOTREACHED();
        break;
    }
  }

  base::FilePath urls_file() { return urls_file_; }
  base::FilePath errors_file() { return errors_file_; }
  base::FilePath stats_file() { return stats_file_; }
  PageCycler* page_cycler() { return page_cycler_; }

 protected:
  base::FilePath temp_path_;
  base::FilePath urls_file_;
  base::FilePath errors_file_;
  base::FilePath stats_file_;
  PageCycler* page_cycler_;
  content::NotificationRegistrar registrar_;
};

// Structure used for testing PageCycler's ability to playback a series of
// URLs given a cache directory.
class PageCyclerCachedBrowserTest : public PageCyclerBrowserTest {
 public:
  // For a cached test, we use the provided user data directory from the test
  // directory.
  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    base::FilePath test_dir;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
    test_dir = test_dir.AppendASCII("page_cycler");

    base::FilePath source_data_dir = test_dir.AppendASCII("cached_data_dir");
    CHECK(file_util::PathExists(source_data_dir));

    CHECK(user_data_dir_.CreateUniqueTempDir());

    base::FilePath dest_data_dir =
        user_data_dir_.path().AppendASCII("cached_data_dir");
    CHECK(!file_util::PathExists(dest_data_dir));

    CHECK(file_util::CopyDirectory(source_data_dir,
                                   user_data_dir_.path(),
                                   true));  // recursive.
    CHECK(file_util::PathExists(dest_data_dir));

    command_line->AppendSwitchPath(switches::kUserDataDir,
                                   dest_data_dir);
    command_line->AppendSwitch(switches::kPlaybackMode);
  }

  // Initialize the file paths to use the UserDataDir's urls file, instead
  // of one to be written.
  virtual void InitFilePaths(base::FilePath temp_path) OVERRIDE {
    urls_file_ = user_data_dir_.path().AppendASCII("cached_data_dir")
                                      .AppendASCII("urls");
    errors_file_ = temp_path.AppendASCII("errors");
    stats_file_ = temp_path.AppendASCII("stats");

    ASSERT_TRUE(file_util::PathExists(urls_file_));
    ASSERT_FALSE(file_util::PathExists(errors_file_));
    ASSERT_FALSE(file_util::PathExists(stats_file_));
  }

 private:
  // The directory storing the copy of the UserDataDir.
  base::ScopedTempDir user_data_dir_;
};

// Sanity check; iterate through a series of URLs and make sure there are no
// errors.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, BasicTest) {
  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::string urls_string = GetStringFromURLs(GetURLs());;

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_FALSE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));
}

// Test to make sure that PageCycler will recognize unvisitable URLs, and will
// handle them appropriately.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, UnvisitableURL) {
  const size_t kNumErrors = 1;
  const char kFakeURL[] = "http://www.pleasenoonehavethisurlanytimeinthenext"
                          "century.com/gibberish";
  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::vector<GURL> urls = GetURLs();
  urls.push_back(GURL(kFakeURL));
  std::string urls_string = GetStringFromURLs(urls);

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();

  ASSERT_EQ(kNumErrors, errors.size());

  // Check that each error message contains the fake URL (i.e., that it wasn't
  // from a valid URL, and that the fake URL was caught each time).
  ASSERT_NE(std::string::npos, errors[0].find(kFakeURL));
}

// Test that PageCycler will remove an invalid URL prior to running.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, InvalidURL) {
  const char kBadURL[] = "notarealurl";

  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::string urls_string = GetStringFromURLs(GetURLs());
  urls_string.append(kBadURL).append("\n");

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();
  ASSERT_EQ(1u, errors.size());

  std::string expected_error = "Omitting invalid URL: ";
  expected_error.append(kBadURL).append(".");

  ASSERT_FALSE(errors[0].compare(expected_error));
}

// Test that PageCycler will remove a Chrome Error URL prior to running.
IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, ChromeErrorURL) {
  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  ASSERT_TRUE(test_server()->Start());

  std::vector<GURL> urls = GetURLs();
  urls.push_back(GURL(content::kUnreachableWebDataURL));
  std::string urls_string = GetStringFromURLs(urls);

  ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(),
                                   urls_string.size()));

  InitPageCycler();
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();
  ASSERT_EQ(1u, errors.size());

  std::string expected_error = "Chrome error pages are not allowed as urls. "
                               "Omitting url: ";
  expected_error.append(content::kUnreachableWebDataURL).append(".");

  ASSERT_FALSE(errors[0].compare(expected_error));
}

#if !defined(OS_CHROMEOS)
// TODO(rdevlin.cronin): Perhaps page cycler isn't completely implemented on
// ChromeOS?

// Test that PageCycler will visit all the urls from a cache directory
// successfully while in playback mode.
// Disabled due to flaky timeouts.  Tracking bugs include
// [ http://crbug.com/159026 ], [ http://crbug.com/131333 ], and
// [ http://crbug.com/222296 ].
IN_PROC_BROWSER_TEST_F(PageCyclerCachedBrowserTest, DISABLED_PlaybackMode) {
#if defined(OS_MACOSX)
  // TODO(kbr): re-enable: http://crbug.com/222296
  if (base::mac::IsOSMountainLionOrLater())
    return;
#endif

  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  InitPageCycler();

  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(stats_file()));
  ASSERT_FALSE(file_util::PathExists(errors_file()));
}
#endif  // !defined(OS_CHROMEOS)

#if !defined(OS_CHROMEOS)
// TODO(rdevlin.cronin): Perhaps page cycler isn't completely implemented on
// ChromeOS?

// Test that PageCycler will have a cache miss if a URL is missing from the
// cache directory while in playback mode.
// Bug 131333: This test fails on a XP debug bot since Build 17609.
#if (defined(OS_WIN) || defined(OS_MACOSX)) && !defined(NDEBUG)
#define MAYBE_URLNotInCache DISABLED_URLNotInCache
#else
#define MAYBE_URLNotInCache URLNotInCache
#endif
IN_PROC_BROWSER_TEST_F(PageCyclerCachedBrowserTest, MAYBE_URLNotInCache) {
  const char kCacheMissURL[] = "http://www.images.google.com/";

  base::ScopedTempDir temp;
  ASSERT_TRUE(temp.CreateUniqueTempDir());

  RegisterForNotifications();
  InitFilePaths(temp.path());

  // Only use a single URL that is not in cache. That's sufficient for the test
  // scenario, and makes things faster than needlessly cycling through all the
  // other URLs.

  base::FilePath new_urls_file = temp.path().AppendASCII("urls");
  ASSERT_FALSE(file_util::PathExists(new_urls_file));

  ASSERT_TRUE(file_util::WriteFile(new_urls_file, kCacheMissURL,
                                   sizeof(kCacheMissURL)));

  InitPageCycler(new_urls_file, errors_file(), stats_file());
  page_cycler()->Run();

  content::RunMessageLoop();
  ASSERT_TRUE(file_util::PathExists(errors_file()));
  ASSERT_TRUE(file_util::PathExists(stats_file()));

  std::vector<std::string> errors = GetErrorsFromFile();
  ASSERT_EQ(1u, errors.size());

  std::string expected_error;
  expected_error.append("Failed to load the page at: ")
      .append(kCacheMissURL)
      .append(": The requested entry was not found in the cache.");

  ASSERT_FALSE(errors[0].compare(expected_error));
}
#endif  // !defined(OS_CHROMEOS)
<|endoftext|>


<commit_before>/** \file   ControlNumberGuesser.cc
 *  \brief  Implementation of the ControlNumberGuesser class.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "ControlNumberGuesser.h"
#include <algorithm>
#include <iterator>
#include <unordered_set>
#include <vector>
#include "StringUtil.h"
#include "TextUtil.h"
#include "util.h"

const std::set<std::string> ControlNumberGuesser::EMPTY_SET;

static kyotocabinet::HashDB *CreateOrOpenKeyValueDB(const std::string &db_path) {
    auto db(new kyotocabinet::HashDB());
    if (not (db->open(db_path, kyotocabinet::HashDB::OWRITER | kyotocabinet::HashDB::OCREATE)))
        LOG_ERROR("failed to open or create \"" + db_path + "\"!");
    return db;
}

static const std::string MATCH_DB_PREFIX("/usr/local/var/lib/tuelib/normalised_");

ControlNumberGuesser::ControlNumberGuesser(const OpenMode open_mode) : title_cursor_(nullptr), author_cursor_(nullptr) {
    const std::string TITLES_DB_PATH(MATCH_DB_PREFIX + "titles.db");
    const std::string AUTHORS_DB_PATH(MATCH_DB_PREFIX + "authors.db");

    if (open_mode == CLEAR_DATABASES) {
        ::unlink(TITLES_DB_PATH.c_str());
        ::unlink(AUTHORS_DB_PATH.c_str());
    }

    titles_db_  = CreateOrOpenKeyValueDB(TITLES_DB_PATH);
    authors_db_ = CreateOrOpenKeyValueDB(AUTHORS_DB_PATH);
}

ControlNumberGuesser::~ControlNumberGuesser() {
    delete title_cursor_; delete author_cursor_; delete titles_db_; delete authors_db_;

    std::unordered_set<std::set<std::string> *> already_deleted;
    for (auto &control_number_and_set_ptr : control_number_to_control_number_set_map_) {
        if (already_deleted.find(control_number_and_set_ptr.second) == already_deleted.end()) {
            delete control_number_and_set_ptr.second;
            already_deleted.emplace(control_number_and_set_ptr.second);
        }
    }
}

void ControlNumberGuesser::insertTitle(const std::string &title, const std::string &control_number) {
    const auto normalised_title(NormaliseTitle(title));
    LOG_DEBUG("normalised_title=\"" + normalised_title + "\".");
    if (unlikely(normalised_title.empty()))
        LOG_WARNING("Empty normalised title in record w/ control number: " + control_number);
    else {
        std::string control_numbers;
        if (titles_db_->get(normalised_title, &control_numbers)) {
            control_numbers += '\0';
            control_numbers += control_number;
        } else
            control_numbers = control_number;
        if (unlikely(not titles_db_->set(normalised_title, control_numbers)))
            LOG_ERROR("failed to insert normalised title into the database!");
    }
}

void ControlNumberGuesser::insertAuthors(const std::set<std::string> &authors, const std::string &control_number) {
    for (const auto author : authors) {
        const auto normalised_author_name(TextUtil::UTF8ToLower(NormaliseAuthorName(author)));
        LOG_DEBUG("normalised_author_name=\"" + normalised_author_name + "\".");
        std::string control_numbers;
        if (authors_db_->get(normalised_author_name, &control_numbers)) {
            control_numbers += '\0';
            control_numbers += control_number;
        } else
            control_numbers = control_number;
        if (unlikely(not authors_db_->set(normalised_author_name, control_numbers)))
            LOG_ERROR("failed to insert normalised author into the database!");
    }
}

std::set<std::string> ControlNumberGuesser::getGuessedControlNumbers(const std::string &title, const std::vector<std::string> &authors) const
{
    const auto normalised_title(NormaliseTitle(title));
    if (logger->getMinimumLogLevel() >= Logger::LL_DEBUG)
        LOG_DEBUG("in ControlNumberGuesser::getGuessedControlNumbers: normalised_title=\"" + normalised_title + "\".");
    std::string concatenated_title_control_numbers;
    std::vector<std::string> title_control_numbers;
    if (not titles_db_->get(normalised_title, &concatenated_title_control_numbers)
        or StringUtil::Split(concatenated_title_control_numbers, '\0', &title_control_numbers) == 0)
        return { };

    std::vector<std::string> all_author_control_numbers;
    for (const auto &author : authors) {
        const auto normalised_author(NormaliseAuthorName(author));
        if (logger->getMinimumLogLevel() >= Logger::LL_DEBUG)
            LOG_DEBUG("in ControlNumberGuesser::getGuessedControlNumbers: normalised_author=\"" + normalised_author + "\".");
        std::string concatenated_author_control_numbers;
        std::set<std::string> author_control_numbers;
        if (authors_db_->get(normalised_author, &concatenated_author_control_numbers)) {
            StringUtil::Split(concatenated_author_control_numbers, '\0', &author_control_numbers);
            for (const auto author_control_number : author_control_numbers)
                all_author_control_numbers.emplace_back(author_control_number);
        }
    }
    if (all_author_control_numbers.empty())
        return { };

    std::sort(title_control_numbers.begin(), title_control_numbers.end());
    std::sort(all_author_control_numbers.begin(), all_author_control_numbers.end());
    std::vector<std::string> common_control_numbers;
    std::set_intersection(title_control_numbers.begin(), title_control_numbers.end(),
                          all_author_control_numbers.begin(), all_author_control_numbers.end(),
                          std::back_inserter(common_control_numbers));

    return std::set<std::string>(common_control_numbers.cbegin(), common_control_numbers.cend());
}

bool ControlNumberGuesser::getNextTitle(std::string * const title, std::set<std::string> * const control_numbers) const {
    if (title_cursor_ == nullptr) {
        title_cursor_ = titles_db_->cursor();
        title_cursor_->jump();
    }

    std::string concatenated_control_numbers;
    if (title_cursor_->get(title, &concatenated_control_numbers, /* Move cursor to the next record */true)) {
        StringUtil::Split(concatenated_control_numbers, '\0', control_numbers);
        return true;
    } else {
        delete title_cursor_;
        title_cursor_ = nullptr;
        return false;
    }
}

bool ControlNumberGuesser::getNextAuthor(std::string * const author_name, std::set<std::string> * const control_numbers) const {
    if (author_cursor_ == nullptr) {
        author_cursor_ = authors_db_->cursor();
        author_cursor_->jump();
    }

    std::string concatenated_control_numbers;
    if (author_cursor_->get(author_name, &concatenated_control_numbers, /* Move cursor to the next record */true)) {
        StringUtil::Split(concatenated_control_numbers, '\0', control_numbers);
        return true;
    } else {
        delete author_cursor_;
        author_cursor_ = nullptr;
        return false;
    }
}

void ControlNumberGuesser::FindDups(const std::unordered_map<std::string, std::set<std::string>> &title_to_control_numbers_map,
                                    const std::unordered_map<std::string, std::set<std::string>> &control_number_to_authors_map) const
{
    for (const auto &title_and_control_numbers : title_to_control_numbers_map) {
        if (title_and_control_numbers.second.size() < 2)
            continue;

        // Collect all control numbers for all authors of the current title:
        std::map<std::string, std::set<std::string>> author_to_control_numbers_map;
        for (const auto &control_number : title_and_control_numbers.second) {
            const auto control_number_and_authors(control_number_to_authors_map.find(control_number));
            if (control_number_and_authors == control_number_to_authors_map.cend())
                continue;

            for (const auto &author : control_number_and_authors->second) {
                auto author_and_control_numbers(author_to_control_numbers_map.find(author));
                if (author_and_control_numbers == author_to_control_numbers_map.end())
                    author_to_control_numbers_map[author] = std::set<std::string>{ control_number };
                else
                    author_and_control_numbers->second.emplace(control_number);
            }
        }

        // Record those cases where we found multiple control numbers for the same author for a single title:
        std::unordered_set<std::string> already_processed_control_numbers;
        for (const auto &author_and_control_numbers : author_to_control_numbers_map) {
            if (author_and_control_numbers.second.size() >= 2) {
                bool skip_author(false);

                // We may have multiple authors for the same work but only wish to report each duplicate work once:
                for (const auto &control_number : author_and_control_numbers.second) {
                    if (already_processed_control_numbers.find(control_number) != already_processed_control_numbers.cend()) {
                        skip_author = true;
                        break;
                    }
                }

                if (not skip_author) {
                    std::set<std::string> *new_set(new std::set<std::string>());
                    for (const auto &control_number : author_and_control_numbers.second) {
                        already_processed_control_numbers.emplace(control_number);
                        new_set->emplace(control_number);
                        control_number_to_control_number_set_map_[control_number] = new_set;
                    }
                }
            }
        }
    }
}

void ControlNumberGuesser::InitControlNumberToControlNumberSetMap() const {
    std::unordered_map<std::string, std::set<std::string>> title_to_control_numbers_map;
    std::string title;
    std::set<std::string> control_numbers;
    while (getNextTitle(&title, &control_numbers))
        title_to_control_numbers_map.emplace(title, control_numbers);

    std::unordered_map<std::string, std::set<std::string>> control_number_to_authors_map;
    std::string author;
    while (getNextAuthor(&author, &control_numbers)) {
        for (const auto &control_number : control_numbers) {
            auto control_number_and_authors(control_number_to_authors_map.find(control_number));
            if (control_number_and_authors == control_number_to_authors_map.end())
                control_number_to_authors_map[control_number] = std::set<std::string>{ author };
            else
                control_number_and_authors->second.emplace(author);
        }
    }

    FindDups(title_to_control_numbers_map, control_number_to_authors_map);
}

const std::set<std::string> &ControlNumberGuesser::getControlNumberPartners(const std::string &control_number) const {
    if (control_number_to_control_number_set_map_.empty())
        InitControlNumberToControlNumberSetMap();

    const auto control_number_and_set_ptr(control_number_to_control_number_set_map_.find(control_number));
    if (control_number_and_set_ptr == control_number_to_control_number_set_map_.cend())
        return EMPTY_SET;
    return *control_number_and_set_ptr->second;
}

std::string ControlNumberGuesser::NormaliseTitle(const std::string &title) {
    std::wstring wtitle;
    if (unlikely(not TextUtil::UTF8ToWCharString(title, &wtitle)))
        LOG_ERROR("failed to convert \"" + title + "\" to a wide character string!");

    std::wstring normalised_title;
    bool space_separator_seen(true);
    for (const auto ch : wtitle) {
        if (TextUtil::IsGeneralPunctuationCharacter(ch) or ch == '-' or TextUtil::IsSpaceSeparatorCharacter(ch)) {
            if (not space_separator_seen)
                normalised_title += ' ';
            space_separator_seen = true;
        } else {
            space_separator_seen = false;
            normalised_title += ch;
        }
    }
    if (not normalised_title.empty() and TextUtil::IsSpaceSeparatorCharacter(normalised_title.back()))
        normalised_title.resize(normalised_title.size() - 1);

    TextUtil::ToLower(&normalised_title);

    std::string utf8_normalised_title;
    if (unlikely(not TextUtil::WCharToUTF8String(normalised_title, &utf8_normalised_title)))
        LOG_ERROR("failed to convert a wstring to an UTF8 string!");

    return utf8_normalised_title;
}

std::string ControlNumberGuesser::NormaliseAuthorName(const std::string &author_name) {
    auto trimmed_author_name = StringUtil::TrimWhite(author_name);
    const auto comma_pos(trimmed_author_name.find(','));
    if (comma_pos != std::string::npos)
        trimmed_author_name = StringUtil::TrimWhite(trimmed_author_name.substr(comma_pos + 1) + " "
                                                    + trimmed_author_name.substr(0, comma_pos));

    std::string normalised_author_name;
    bool space_seen(false);
    unsigned non_space_sequence_length(0);
    for (const char ch : trimmed_author_name) {
        switch (ch) {
        case '.':
            if (non_space_sequence_length == 1)
                normalised_author_name.resize(normalised_author_name.length() - 1);
            else
                normalised_author_name += ch;
            if (normalised_author_name.empty())
                space_seen = false;
            else {
                if (normalised_author_name.back() != ' ')
                    normalised_author_name += ' ';
                space_seen = true;
            }
            non_space_sequence_length = 0;
            break;
        case ' ':
            if (not space_seen)
                normalised_author_name += ' ';
            space_seen = true;
            non_space_sequence_length = 0;
            break;
        default:
            normalised_author_name += ch;
            space_seen = false;
            ++non_space_sequence_length;
        }
    }

    return TextUtil::UTF8ToLower(normalised_author_name);
}
<commit_msg>Used new ligature expansion code to improve title and author name normalisations.<commit_after>/** \file   ControlNumberGuesser.cc
 *  \brief  Implementation of the ControlNumberGuesser class.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "ControlNumberGuesser.h"
#include <algorithm>
#include <iterator>
#include <unordered_set>
#include <vector>
#include "StringUtil.h"
#include "TextUtil.h"
#include "util.h"

const std::set<std::string> ControlNumberGuesser::EMPTY_SET;

static kyotocabinet::HashDB *CreateOrOpenKeyValueDB(const std::string &db_path) {
    auto db(new kyotocabinet::HashDB());
    if (not (db->open(db_path, kyotocabinet::HashDB::OWRITER | kyotocabinet::HashDB::OCREATE)))
        LOG_ERROR("failed to open or create \"" + db_path + "\"!");
    return db;
}

static const std::string MATCH_DB_PREFIX("/usr/local/var/lib/tuelib/normalised_");

ControlNumberGuesser::ControlNumberGuesser(const OpenMode open_mode) : title_cursor_(nullptr), author_cursor_(nullptr) {
    const std::string TITLES_DB_PATH(MATCH_DB_PREFIX + "titles.db");
    const std::string AUTHORS_DB_PATH(MATCH_DB_PREFIX + "authors.db");

    if (open_mode == CLEAR_DATABASES) {
        ::unlink(TITLES_DB_PATH.c_str());
        ::unlink(AUTHORS_DB_PATH.c_str());
    }

    titles_db_  = CreateOrOpenKeyValueDB(TITLES_DB_PATH);
    authors_db_ = CreateOrOpenKeyValueDB(AUTHORS_DB_PATH);
}

ControlNumberGuesser::~ControlNumberGuesser() {
    delete title_cursor_; delete author_cursor_; delete titles_db_; delete authors_db_;

    std::unordered_set<std::set<std::string> *> already_deleted;
    for (auto &control_number_and_set_ptr : control_number_to_control_number_set_map_) {
        if (already_deleted.find(control_number_and_set_ptr.second) == already_deleted.end()) {
            delete control_number_and_set_ptr.second;
            already_deleted.emplace(control_number_and_set_ptr.second);
        }
    }
}

void ControlNumberGuesser::insertTitle(const std::string &title, const std::string &control_number) {
    const auto normalised_title(NormaliseTitle(title));
    LOG_DEBUG("normalised_title=\"" + normalised_title + "\".");
    if (unlikely(normalised_title.empty()))
        LOG_WARNING("Empty normalised title in record w/ control number: " + control_number);
    else {
        std::string control_numbers;
        if (titles_db_->get(normalised_title, &control_numbers)) {
            control_numbers += '\0';
            control_numbers += control_number;
        } else
            control_numbers = control_number;
        if (unlikely(not titles_db_->set(normalised_title, control_numbers)))
            LOG_ERROR("failed to insert normalised title into the database!");
    }
}

void ControlNumberGuesser::insertAuthors(const std::set<std::string> &authors, const std::string &control_number) {
    for (const auto author : authors) {
        const auto normalised_author_name(TextUtil::UTF8ToLower(NormaliseAuthorName(author)));
        LOG_DEBUG("normalised_author_name=\"" + normalised_author_name + "\".");
        std::string control_numbers;
        if (authors_db_->get(normalised_author_name, &control_numbers)) {
            control_numbers += '\0';
            control_numbers += control_number;
        } else
            control_numbers = control_number;
        if (unlikely(not authors_db_->set(normalised_author_name, control_numbers)))
            LOG_ERROR("failed to insert normalised author into the database!");
    }
}

std::set<std::string> ControlNumberGuesser::getGuessedControlNumbers(const std::string &title, const std::vector<std::string> &authors) const
{
    const auto normalised_title(NormaliseTitle(title));
    if (logger->getMinimumLogLevel() >= Logger::LL_DEBUG)
        LOG_DEBUG("in ControlNumberGuesser::getGuessedControlNumbers: normalised_title=\"" + normalised_title + "\".");
    std::string concatenated_title_control_numbers;
    std::vector<std::string> title_control_numbers;
    if (not titles_db_->get(normalised_title, &concatenated_title_control_numbers)
        or StringUtil::Split(concatenated_title_control_numbers, '\0', &title_control_numbers) == 0)
    {
        LOG_DEBUG("no entries found for normalised title");
        return { };
    }

    std::vector<std::string> all_author_control_numbers;
    for (const auto &author : authors) {
        const auto normalised_author(NormaliseAuthorName(author));
        if (logger->getMinimumLogLevel() >= Logger::LL_DEBUG)
            LOG_DEBUG("in ControlNumberGuesser::getGuessedControlNumbers: normalised_author=\"" + normalised_author + "\".");
        std::string concatenated_author_control_numbers;
        std::set<std::string> author_control_numbers;
        if (authors_db_->get(normalised_author, &concatenated_author_control_numbers)) {
            StringUtil::Split(concatenated_author_control_numbers, '\0', &author_control_numbers);
            for (const auto author_control_number : author_control_numbers)
                all_author_control_numbers.emplace_back(author_control_number);
        }
    }
    if (all_author_control_numbers.empty())
        return { };

    std::sort(title_control_numbers.begin(), title_control_numbers.end());
    std::sort(all_author_control_numbers.begin(), all_author_control_numbers.end());
    std::vector<std::string> common_control_numbers;
    std::set_intersection(title_control_numbers.begin(), title_control_numbers.end(),
                          all_author_control_numbers.begin(), all_author_control_numbers.end(),
                          std::back_inserter(common_control_numbers));

    return std::set<std::string>(common_control_numbers.cbegin(), common_control_numbers.cend());
}

bool ControlNumberGuesser::getNextTitle(std::string * const title, std::set<std::string> * const control_numbers) const {
    if (title_cursor_ == nullptr) {
        title_cursor_ = titles_db_->cursor();
        title_cursor_->jump();
    }

    std::string concatenated_control_numbers;
    if (title_cursor_->get(title, &concatenated_control_numbers, /* Move cursor to the next record */true)) {
        StringUtil::Split(concatenated_control_numbers, '\0', control_numbers);
        return true;
    } else {
        delete title_cursor_;
        title_cursor_ = nullptr;
        return false;
    }
}

bool ControlNumberGuesser::getNextAuthor(std::string * const author_name, std::set<std::string> * const control_numbers) const {
    if (author_cursor_ == nullptr) {
        author_cursor_ = authors_db_->cursor();
        author_cursor_->jump();
    }

    std::string concatenated_control_numbers;
    if (author_cursor_->get(author_name, &concatenated_control_numbers, /* Move cursor to the next record */true)) {
        StringUtil::Split(concatenated_control_numbers, '\0', control_numbers);
        return true;
    } else {
        delete author_cursor_;
        author_cursor_ = nullptr;
        return false;
    }
}

void ControlNumberGuesser::FindDups(const std::unordered_map<std::string, std::set<std::string>> &title_to_control_numbers_map,
                                    const std::unordered_map<std::string, std::set<std::string>> &control_number_to_authors_map) const
{
    for (const auto &title_and_control_numbers : title_to_control_numbers_map) {
        if (title_and_control_numbers.second.size() < 2)
            continue;

        // Collect all control numbers for all authors of the current title:
        std::map<std::string, std::set<std::string>> author_to_control_numbers_map;
        for (const auto &control_number : title_and_control_numbers.second) {
            const auto control_number_and_authors(control_number_to_authors_map.find(control_number));
            if (control_number_and_authors == control_number_to_authors_map.cend())
                continue;

            for (const auto &author : control_number_and_authors->second) {
                auto author_and_control_numbers(author_to_control_numbers_map.find(author));
                if (author_and_control_numbers == author_to_control_numbers_map.end())
                    author_to_control_numbers_map[author] = std::set<std::string>{ control_number };
                else
                    author_and_control_numbers->second.emplace(control_number);
            }
        }

        // Record those cases where we found multiple control numbers for the same author for a single title:
        std::unordered_set<std::string> already_processed_control_numbers;
        for (const auto &author_and_control_numbers : author_to_control_numbers_map) {
            if (author_and_control_numbers.second.size() >= 2) {
                bool skip_author(false);

                // We may have multiple authors for the same work but only wish to report each duplicate work once:
                for (const auto &control_number : author_and_control_numbers.second) {
                    if (already_processed_control_numbers.find(control_number) != already_processed_control_numbers.cend()) {
                        skip_author = true;
                        break;
                    }
                }

                if (not skip_author) {
                    std::set<std::string> *new_set(new std::set<std::string>());
                    for (const auto &control_number : author_and_control_numbers.second) {
                        already_processed_control_numbers.emplace(control_number);
                        new_set->emplace(control_number);
                        control_number_to_control_number_set_map_[control_number] = new_set;
                    }
                }
            }
        }
    }
}

void ControlNumberGuesser::InitControlNumberToControlNumberSetMap() const {
    std::unordered_map<std::string, std::set<std::string>> title_to_control_numbers_map;
    std::string title;
    std::set<std::string> control_numbers;
    while (getNextTitle(&title, &control_numbers))
        title_to_control_numbers_map.emplace(title, control_numbers);

    std::unordered_map<std::string, std::set<std::string>> control_number_to_authors_map;
    std::string author;
    while (getNextAuthor(&author, &control_numbers)) {
        for (const auto &control_number : control_numbers) {
            auto control_number_and_authors(control_number_to_authors_map.find(control_number));
            if (control_number_and_authors == control_number_to_authors_map.end())
                control_number_to_authors_map[control_number] = std::set<std::string>{ author };
            else
                control_number_and_authors->second.emplace(author);
        }
    }

    FindDups(title_to_control_numbers_map, control_number_to_authors_map);
}

const std::set<std::string> &ControlNumberGuesser::getControlNumberPartners(const std::string &control_number) const {
    if (control_number_to_control_number_set_map_.empty())
        InitControlNumberToControlNumberSetMap();

    const auto control_number_and_set_ptr(control_number_to_control_number_set_map_.find(control_number));
    if (control_number_and_set_ptr == control_number_to_control_number_set_map_.cend())
        return EMPTY_SET;
    return *control_number_and_set_ptr->second;
}

std::string ControlNumberGuesser::NormaliseTitle(const std::string &title) {
    std::wstring wtitle;
    if (unlikely(not TextUtil::UTF8ToWCharString(title, &wtitle)))
        LOG_ERROR("failed to convert \"" + title + "\" to a wide character string!");

    std::wstring normalised_title;
    bool space_separator_seen(true);
    for (const auto ch : wtitle) {
        if (TextUtil::IsGeneralPunctuationCharacter(ch) or ch == '-' or TextUtil::IsSpaceSeparatorCharacter(ch)) {
            if (not space_separator_seen)
                normalised_title += ' ';
            space_separator_seen = true;
        } else {
            space_separator_seen = false;
            normalised_title += ch;
        }
    }
    if (not normalised_title.empty() and TextUtil::IsSpaceSeparatorCharacter(normalised_title.back()))
        normalised_title.resize(normalised_title.size() - 1);
    normalised_title = TextUtil::ExpandLigatures(normalised_title);

    TextUtil::ToLower(&normalised_title);

    std::string utf8_normalised_title;
    if (unlikely(not TextUtil::WCharToUTF8String(normalised_title, &utf8_normalised_title)))
        LOG_ERROR("failed to convert a wstring to an UTF8 string!");

    return utf8_normalised_title;
}

std::string ControlNumberGuesser::NormaliseAuthorName(const std::string &author_name) {
    auto trimmed_author_name = StringUtil::TrimWhite(author_name);
    const auto comma_pos(trimmed_author_name.find(','));
    if (comma_pos != std::string::npos)
        trimmed_author_name = StringUtil::TrimWhite(trimmed_author_name.substr(comma_pos + 1) + " "
                                                    + trimmed_author_name.substr(0, comma_pos));

    std::string normalised_author_name;
    bool space_seen(false);
    unsigned non_space_sequence_length(0);
    for (const char ch : trimmed_author_name) {
        switch (ch) {
        case '.':
            if (non_space_sequence_length == 1)
                normalised_author_name.resize(normalised_author_name.length() - 1);
            else
                normalised_author_name += ch;
            if (normalised_author_name.empty())
                space_seen = false;
            else {
                if (normalised_author_name.back() != ' ')
                    normalised_author_name += ' ';
                space_seen = true;
            }
            non_space_sequence_length = 0;
            break;
        case ' ':
            if (not space_seen)
                normalised_author_name += ' ';
            space_seen = true;
            non_space_sequence_length = 0;
            break;
        default:
            normalised_author_name += ch;
            space_seen = false;
            ++non_space_sequence_length;
        }
    }
    normalised_author_name = TextUtil::ExpandLigatures(normalised_author_name);

    return TextUtil::UTF8ToLower(&normalised_author_name);
    LOG_DEBUG("normalised author name=\"" + normalised_author_name + "\"");

    return normalised_author_name;
}
<|endoftext|>


<commit_before>/* Copyright (c) 2016, the Cap authors.
 *
 * This file is subject to the Modified BSD License and may not be distributed
 * without copyright and license information. Please refer to the file LICENSE
 * for the text and further information on this license.
 */

#define BOOST_TEST_MODULE EquivalentCircuit

#include "main.cc"

#include <cap/energy_storage_device.h>
#include <cap/equivalent_circuit.h>
#include <boost/property_tree/info_parser.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/range/combine.hpp>
#include <boost/algorithm/cxx11/is_sorted.hpp>

BOOST_DATA_TEST_CASE( test_equivalent_circuit,
    boost::unit_test::data::make({true, false}),
    with_faradaic_processes )
{
    boost::mpi::communicator world;

    // parse input file
    boost::property_tree::ptree input_database;
    boost::property_tree::info_parser::read_info("equivalent_circuits.info",
                                                 input_database);

    // read the property tree for building the supercapacitor
    boost::property_tree::ptree super_capacitor_database;
    boost::property_tree::info_parser::read_info("super_capacitor.info",
                                                 super_capacitor_database);
    if (!with_faradaic_processes)
        super_capacitor_database.put("material_properties.electrode_material"
                                     ".exchange_current_density", 0.0);

    // database passed to compute_equivalent_circuit(...) must be empty
    boost::property_tree::ptree not_empty_database;
    not_empty_database.put("something", "not_empty");
    BOOST_CHECK_THROW(
        cap::compute_equivalent_circuit(super_capacitor_database,
                                        not_empty_database),
        std::runtime_error);

    // get the property tree to build the equivalent circuit
    boost::property_tree::ptree equivalent_circuit_database;
    cap::compute_equivalent_circuit(super_capacitor_database,
                                    equivalent_circuit_database);

    if (with_faradaic_processes)
        BOOST_TEST(
            equivalent_circuit_database.get<std::string>("type").compare("ParallelRC")
                == 0);
    else
        BOOST_TEST(
            equivalent_circuit_database.get<std::string>("type").compare("SeriesRC")
                == 0);

    std::shared_ptr<cap::EnergyStorageDevice> super_capacitor =
        cap::EnergyStorageDevice::build(world, super_capacitor_database);
    std::shared_ptr<cap::EnergyStorageDevice> equivalent_circuit =
        cap::EnergyStorageDevice::build(world, equivalent_circuit_database);

    double time_step = 0.1; // [second]
    double const maximum_duration = 15.0; // [second]
    double const charge_current = 10.0e-3; // [ampere]
    double const charge_stop_at = 2.0; // [volt]
    auto charge_done =
        [charge_stop_at](std::shared_ptr<cap::EnergyStorageDevice const> dev)
        {
            double voltage;
            dev->get_voltage(voltage);
            return voltage >= charge_stop_at;
        };
    auto report =
        [](double const time,
           std::shared_ptr<cap::EnergyStorageDevice const> dev,
           std::vector<double> & data)
        {
            std::ignore = time;
            double voltage;
            dev->get_voltage(voltage);
            data.push_back(voltage);
        };

    std::map<std::string, std::shared_ptr<cap::EnergyStorageDevice>> device;
    device.emplace("super_capacitor",
        cap::EnergyStorageDevice::build(world, super_capacitor_database));
    device.emplace("equivalent_circuit",
        cap::EnergyStorageDevice::build(world, equivalent_circuit_database));

    std::map<std::string, std::vector<double>> data;
    data.emplace("super_capacitor",
       std::vector<double>());
    data.emplace("equivalent_circuit",
       std::vector<double>());

    double time = 0.0;
    while (time < maximum_duration)
    {
        time += time_step;
        for (auto type : { "super_capacitor", "equivalent_circuit" })
        {
            device[type]->evolve_one_time_step_constant_current(time_step,
                                                                charge_current);
            report(time, device[type], data[type]);
        }
        if (charge_done(device["super_capacitor"]) &&
            charge_done(device["equivalent_circuit"]))
            break;
    }

    auto absolute_difference =
        [](boost::tuple<double, double> const & a)
        {
            double a1, a2;
            boost::tie(a1, a2) = a;
            return std::abs(a1 - a2);
        };

    // a = (a1, a2) and b = (b1, b2)
    // check that |a1 - a2| >= |b1 - b2|
    auto compare =
        [absolute_difference](boost::tuple<double, double> const & a,
           boost::tuple<double, double> const & b) -> bool
        {
            return absolute_difference(a) >= absolute_difference(b);
        };

    // check that the voltage difference between the two model is a decreasing
    // sequence
    BOOST_TEST(
        boost::algorithm::is_sorted(
            boost::combine(data["super_capacitor"],
                           data["equivalent_circuit"]),
            compare));

   // check that the voltage is the same asymptotically (say one percent)
   BOOST_TEST(
        data["super_capacitor"].back() == data["equivalent_circuit"].back(),
        1.0% boost::test_tools::tolerance());

}
<commit_msg>forgot to remove unused input<commit_after>/* Copyright (c) 2016, the Cap authors.
 *
 * This file is subject to the Modified BSD License and may not be distributed
 * without copyright and license information. Please refer to the file LICENSE
 * for the text and further information on this license.
 */

#define BOOST_TEST_MODULE EquivalentCircuit

#include "main.cc"

#include <cap/energy_storage_device.h>
#include <cap/equivalent_circuit.h>
#include <boost/property_tree/info_parser.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/range/combine.hpp>
#include <boost/algorithm/cxx11/is_sorted.hpp>

BOOST_DATA_TEST_CASE( test_equivalent_circuit,
    boost::unit_test::data::make({true, false}),
    with_faradaic_processes )
{
    boost::mpi::communicator world;

    // read the property tree for building the supercapacitor
    boost::property_tree::ptree super_capacitor_database;
    boost::property_tree::info_parser::read_info("super_capacitor.info",
                                                 super_capacitor_database);
    if (!with_faradaic_processes)
        super_capacitor_database.put("material_properties.electrode_material"
                                     ".exchange_current_density", 0.0);

    // database passed to compute_equivalent_circuit(...) must be empty
    boost::property_tree::ptree not_empty_database;
    not_empty_database.put("something", "not_empty");
    BOOST_CHECK_THROW(
        cap::compute_equivalent_circuit(super_capacitor_database,
                                        not_empty_database),
        std::runtime_error);

    // get the property tree to build the equivalent circuit
    boost::property_tree::ptree equivalent_circuit_database;
    cap::compute_equivalent_circuit(super_capacitor_database,
                                    equivalent_circuit_database);

    if (with_faradaic_processes)
        BOOST_TEST(
            equivalent_circuit_database.get<std::string>("type").compare("ParallelRC")
                == 0);
    else
        BOOST_TEST(
            equivalent_circuit_database.get<std::string>("type").compare("SeriesRC")
                == 0);

    std::shared_ptr<cap::EnergyStorageDevice> super_capacitor =
        cap::EnergyStorageDevice::build(world, super_capacitor_database);
    std::shared_ptr<cap::EnergyStorageDevice> equivalent_circuit =
        cap::EnergyStorageDevice::build(world, equivalent_circuit_database);

    double time_step = 0.1; // [second]
    double const maximum_duration = 15.0; // [second]
    double const charge_current = 10.0e-3; // [ampere]
    double const charge_stop_at = 2.0; // [volt]
    auto charge_done =
        [charge_stop_at](std::shared_ptr<cap::EnergyStorageDevice const> dev)
        {
            double voltage;
            dev->get_voltage(voltage);
            return voltage >= charge_stop_at;
        };
    auto report =
        [](double const time,
           std::shared_ptr<cap::EnergyStorageDevice const> dev,
           std::vector<double> & data)
        {
            std::ignore = time;
            double voltage;
            dev->get_voltage(voltage);
            data.push_back(voltage);
        };

    std::map<std::string, std::shared_ptr<cap::EnergyStorageDevice>> device;
    device.emplace("super_capacitor",
        cap::EnergyStorageDevice::build(world, super_capacitor_database));
    device.emplace("equivalent_circuit",
        cap::EnergyStorageDevice::build(world, equivalent_circuit_database));

    std::map<std::string, std::vector<double>> data;
    data.emplace("super_capacitor",
       std::vector<double>());
    data.emplace("equivalent_circuit",
       std::vector<double>());

    double time = 0.0;
    while (time < maximum_duration)
    {
        time += time_step;
        for (auto type : { "super_capacitor", "equivalent_circuit" })
        {
            device[type]->evolve_one_time_step_constant_current(time_step,
                                                                charge_current);
            report(time, device[type], data[type]);
        }
        if (charge_done(device["super_capacitor"]) &&
            charge_done(device["equivalent_circuit"]))
            break;
    }

    auto absolute_difference =
        [](boost::tuple<double, double> const & a)
        {
            double a1, a2;
            boost::tie(a1, a2) = a;
            return std::abs(a1 - a2);
        };

    // a = (a1, a2) and b = (b1, b2)
    // check that |a1 - a2| >= |b1 - b2|
    auto compare =
        [absolute_difference](boost::tuple<double, double> const & a,
           boost::tuple<double, double> const & b) -> bool
        {
            return absolute_difference(a) >= absolute_difference(b);
        };

    // check that the voltage difference between the two model is a decreasing
    // sequence
    BOOST_TEST(
        boost::algorithm::is_sorted(
            boost::combine(data["super_capacitor"],
                           data["equivalent_circuit"]),
            compare));

   // check that the voltage is the same asymptotically (say one percent)
   BOOST_TEST(
        data["super_capacitor"].back() == data["equivalent_circuit"].back(),
        1.0% boost::test_tools::tolerance());

}
<|endoftext|>