New fileutils.h method for managing resources on different platforms

Review URL: http://webrtc-codereview.appspot.com/304007

git-svn-id: http://webrtc.googlecode.com/svn/trunk@1105 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/test/testsupport/fileutils.cc b/test/testsupport/fileutils.cc
index 09d7baa..84e9593 100644
--- a/test/testsupport/fileutils.cc
+++ b/test/testsupport/fileutils.cc
@@ -25,6 +25,8 @@
 
 #include <cstdio>
 
+#include "typedefs.h"  // For architecture defines
+
 namespace webrtc {
 namespace test {
 
@@ -36,18 +38,17 @@
 // The file we're looking for to identify the project root dir.
 static const char* kProjectRootFileName = "DEPS";
 static const char* kOutputDirName = "out";
-static const char* kOutputFallbackPath = "./";
+static const char* kFallbackPath = "./";
+static const char* kResourcesDirName = "resources";
 const char* kCannotFindProjectRootDir = "ERROR_CANNOT_FIND_PROJECT_ROOT_DIR";
 
 std::string ProjectRootPath() {
-  char path_buffer[FILENAME_MAX];
-  if (!GET_CURRENT_DIR(path_buffer, sizeof(path_buffer))) {
-    fprintf(stderr, "Cannot get current directory!\n");
+  std::string working_dir = WorkingDir();
+  if (working_dir == kFallbackPath) {
     return kCannotFindProjectRootDir;
   }
-
   // Check for our file that verifies the root dir.
-  std::string current_path(path_buffer);
+  std::string current_path(working_dir);
   FILE* file = NULL;
   int path_delimiter_index = current_path.find_last_of(kPathDelimiter);
   while (path_delimiter_index > -1) {
@@ -58,12 +59,10 @@
       fclose(file);
       return current_path + kPathDelimiter;
     }
-
     // Move up one directory in the directory tree.
     current_path = current_path.substr(0, path_delimiter_index);
     path_delimiter_index = current_path.find_last_of(kPathDelimiter);
   }
-
   // Reached the root directory.
   fprintf(stderr, "Cannot find project root directory!\n");
   return kCannotFindProjectRootDir;
@@ -72,25 +71,85 @@
 std::string OutputPath() {
   std::string path = ProjectRootPath();
   if (path == kCannotFindProjectRootDir) {
-    return kOutputFallbackPath;
+    return kFallbackPath;
   }
   path += kOutputDirName;
-  struct stat path_info = {0};
-  // Check if the path exists already:
-  if (stat(path.c_str(), &path_info) == 0) {
-    if (!S_ISDIR(path_info.st_mode)) {
-      fprintf(stderr, "Path %s exists but is not a directory! Remove this file "
-              "and re-run to create the output folder.\n", path.c_str());
-      return kOutputFallbackPath;
-    }
-  } else {
-#ifdef WIN32
-      _mkdir(path.c_str());
-#else
-      mkdir(path.c_str(),  S_IRWXU | S_IRWXG | S_IRWXO);
-#endif
+  if (!CreateDirectory(path)) {
+    return kFallbackPath;
   }
   return path + kPathDelimiter;
 }
+
+std::string WorkingDir() {
+  char path_buffer[FILENAME_MAX];
+  if (!GET_CURRENT_DIR(path_buffer, sizeof(path_buffer))) {
+    fprintf(stderr, "Cannot get current directory!\n");
+    return kFallbackPath;
+  }
+  else {
+    return std::string(path_buffer);
+  }
+}
+
+bool CreateDirectory(std::string directory_name) {
+  struct stat path_info = {0};
+  // Check if the path exists already:
+  if (stat(directory_name.c_str(), &path_info) == 0) {
+    if (!S_ISDIR(path_info.st_mode)) {
+      fprintf(stderr, "Path %s exists but is not a directory! Remove this "
+              "file and re-run to create the directory.\n",
+              directory_name.c_str());
+      return false;
+    }
+  } else {
+#ifdef WIN32
+    return _mkdir(directory_name.c_str()) == 0;
+#else
+    return mkdir(directory_name.c_str(),  S_IRWXU | S_IRWXG | S_IRWXO) == 0;
+#endif
+  }
+  return true;
+}
+
+bool FileExists(std::string file_name) {
+  struct stat file_info = {0};
+  return stat(file_name.c_str(), &file_info) == 0;
+}
+
+std::string ResourcePath(std::string name, std::string extension) {
+  std::string platform = "win";
+#ifdef WEBRTC_LINUX
+  platform = "linux";
+#endif  // WEBRTC_LINUX
+#ifdef WEBRTC_MAC
+  platform = "mac";
+#endif  // WEBRTC_MAC
+
+#ifdef WEBRTC_ARCH_64_BITS
+  std::string architecture = "64";
+#else
+  std::string architecture = "32";
+#endif  // WEBRTC_ARCH_64_BITS
+
+  std::string resources_path = ProjectRootPath() + kResourcesDirName + kPathDelimiter;
+  std::string resource_file = resources_path + name + "_" + platform + "_" +
+      architecture + "." + extension;
+  if (!FileExists(resource_file)) {
+    return resource_file;
+  }
+  // Try without architecture.
+  resource_file = resources_path + name + "_" + platform + "." + extension;
+  if (FileExists(resource_file)) {
+    return resource_file;
+  }
+  // Try without platform.
+  resource_file = resources_path + name + "_" + architecture + "." + extension;
+  if (FileExists(resource_file)) {
+    return resource_file;
+  }
+  // Fall back on name without architecture or platform.
+  return resources_path + name + "." + extension;
+}
+
 }  // namespace test
 }  // namespace webrtc
diff --git a/test/testsupport/fileutils.h b/test/testsupport/fileutils.h
index a111a7a..cd43c69 100644
--- a/test/testsupport/fileutils.h
+++ b/test/testsupport/fileutils.h
@@ -9,6 +9,7 @@
  */
 
 // File utilities for testing purposes.
+//
 // The ProjectRootPath() method is a convenient way of getting an absolute
 // path to the project source tree root directory. Using this, it is easy to
 // refer to test resource files in a portable way.
@@ -99,6 +100,37 @@
 // found, the current working directory ("./") is returned as a fallback.
 std::string OutputPath();
 
+// Returns a path to a resource file for the currently executing platform.
+// Adapts to what filenames are currently present in the
+// [project-root]/resources/ dir.
+// Returns an absolute path according to this priority list (the directory
+// part of the path is left out for readability):
+// 1. [name]_[platform]_[architecture].[extension]
+// 2. [name]_[platform].[extension]
+// 3. [name]_[architecture].[extension]
+// 4. [name].[extension]
+// Where
+// * platform is either of "win", "mac" or "linux".
+// * architecture is either of "32" or "64".
+//
+// Arguments:
+//    name - Name of the resource file. If a plain filename (no directory path)
+//           is supplied, the file is assumed to be located in resources/
+//           If a directory path is prepended to the filename, a subdirectory
+//           hierarchy reflecting that path is assumed to be present.
+//    extension - File extension, without the dot, i.e. "bmp" or "yuv".
+std::string ResourcePath(std::string name, std::string extension);
+
+// Gets the current working directory for the executing program.
+// Returns "./" if for some reason it is not possible to find the working
+// directory.
+std::string WorkingDir();
+
+// Creates a directory if it not already exists.
+// Returns true if successful. Will print an error message to stderr and return
+// false if a file with the same name already exists.
+bool CreateDirectory(std::string directory_name);
+
 }  // namespace test
 }  // namespace webrtc
 
diff --git a/test/testsupport/fileutils_unittest.cc b/test/testsupport/fileutils_unittest.cc
index 493256b..e9aa03c 100644
--- a/test/testsupport/fileutils_unittest.cc
+++ b/test/testsupport/fileutils_unittest.cc
@@ -9,52 +9,99 @@
  */
 
 #include <cstdio>
-#include "fileutils.h"
+#include <list>
+#include <string>
+
 #include "gtest/gtest.h"
+#include "testsupport/fileutils.h"
 
 #ifdef WIN32
-#include <direct.h>
-#define GET_CURRENT_DIR _getcwd
 static const char* kPathDelimiter = "\\";
 #else
-#include <unistd.h>
-#define GET_CURRENT_DIR getcwd
 static const char* kPathDelimiter = "/";
 #endif
 
+static const std::string kDummyDir = "file_utils_unittest_dummy_dir";
+static const std::string kResourcesDir = "resources";
+static const std::string kTestName = "fileutils_unittest";
+static const std::string kExtension = "tmp";
+
+typedef std::list<std::string> FileList;
+
 namespace webrtc {
-namespace test {
 
 // Test fixture to restore the working directory between each test, since some
 // of them change it with chdir during execution (not restored by the
 // gtest framework).
-class FileUtilsTest: public testing::Test {
+class FileUtilsTest : public testing::Test {
  protected:
   FileUtilsTest() {
-    original_working_dir_ = GetWorkingDir();
   }
   virtual ~FileUtilsTest() {}
+  // Runs before the first test
+  static void SetUpTestCase() {
+    original_working_dir_ = webrtc::test::WorkingDir();
+    std::string resources_path = original_working_dir_ + kPathDelimiter +
+        kResourcesDir + kPathDelimiter;
+    webrtc::test::CreateDirectory(resources_path);
+
+    files_.push_back(resources_path + kTestName + "." + kExtension);
+    files_.push_back(resources_path + kTestName + "_32." + kExtension);
+    files_.push_back(resources_path + kTestName + "_64." + kExtension);
+    files_.push_back(resources_path + kTestName + "_linux." + kExtension);
+    files_.push_back(resources_path + kTestName + "_mac." + kExtension);
+    files_.push_back(resources_path + kTestName + "_win." + kExtension);
+    files_.push_back(resources_path + kTestName + "_linux_32." + kExtension);
+    files_.push_back(resources_path + kTestName + "_mac_32." + kExtension);
+    files_.push_back(resources_path + kTestName + "_win_32." + kExtension);
+    files_.push_back(resources_path + kTestName + "_linux_64." + kExtension);
+    files_.push_back(resources_path + kTestName + "_mac_64." + kExtension);
+    files_.push_back(resources_path + kTestName + "_win_64." + kExtension);
+
+    // Now that the resources dir exists, write some empty test files into it.
+    for (FileList::iterator file_it = files_.begin();
+        file_it != files_.end(); ++file_it) {
+      FILE* file = fopen(file_it->c_str(), "wb");
+      ASSERT_TRUE(file != NULL) << "Failed to write file: " << file_it->c_str();
+      ASSERT_TRUE(fprintf(file, "%s",  "Dummy data") > 0);
+      fclose(file);
+    }
+    // Create a dummy subdir that can be chdir'ed into for testing purposes.
+    empty_dummy_dir_ = original_working_dir_ + kPathDelimiter + kDummyDir;
+    webrtc::test::CreateDirectory(empty_dummy_dir_);
+  }
+  static void TearDownTestCase() {
+    // Clean up all resource files written
+    for (FileList::iterator file_it = files_.begin();
+            file_it != files_.end(); ++file_it) {
+      remove(file_it->c_str());
+    }
+    std::remove(empty_dummy_dir_.c_str());
+  }
   void SetUp() {
-    chdir(original_working_dir_.c_str());
+    ASSERT_EQ(chdir(original_working_dir_.c_str()), 0);
   }
-  void TearDown() {}
+  void TearDown() {
+    ASSERT_EQ(chdir(original_working_dir_.c_str()), 0);
+  }
+ protected:
+  static std::string empty_dummy_dir_;
  private:
-  std::string original_working_dir_;
-  static std::string GetWorkingDir() {
-    char path_buffer[FILENAME_MAX];
-    EXPECT_TRUE(GET_CURRENT_DIR(path_buffer, sizeof(path_buffer)))
-      << "Cannot get current working directory!";
-    return std::string(path_buffer);
-  }
+  static FileList files_;
+  static std::string original_working_dir_;
 };
 
+FileList FileUtilsTest::files_;
+std::string FileUtilsTest::original_working_dir_ = "";
+std::string FileUtilsTest::empty_dummy_dir_ = "";
+
 // Tests that the project root path is returned for the default working
 // directory that is automatically set when the test executable is launched.
 // The test is not fully testing the implementation, since we cannot be sure
 // of where the executable was launched from.
 // The test will fail if the top level directory is not named "trunk".
 TEST_F(FileUtilsTest, ProjectRootPathFromUnchangedWorkingDir) {
-  std::string path = ProjectRootPath();
+  std::string path = webrtc::test::ProjectRootPath();
   std::string expected_end = "trunk";
   expected_end = kPathDelimiter + expected_end + kPathDelimiter;
   ASSERT_EQ(path.length() - expected_end.length(), path.find(expected_end));
@@ -62,7 +109,7 @@
 
 // Similar to the above test, but for the output dir
 TEST_F(FileUtilsTest, OutputPathFromUnchangedWorkingDir) {
-  std::string path = OutputPath();
+  std::string path = webrtc::test::OutputPath();
   std::string expected_end = "out";
   expected_end = kPathDelimiter + expected_end + kPathDelimiter;
   ASSERT_EQ(path.length() - expected_end.length(), path.find(expected_end));
@@ -72,21 +119,19 @@
 // deeper from the current one. Then testing that the project path returned
 // is still the same, when the function under test is called again.
 TEST_F(FileUtilsTest, ProjectRootPathFromDeeperWorkingDir) {
-  std::string path = ProjectRootPath();
+  std::string path = webrtc::test::ProjectRootPath();
   std::string original_working_dir = path;  // This is the correct project root
-  // Change to a subdirectory path (the full path doesn't have to exist).
-  path += "foo/bar/baz";
-  chdir(path.c_str());
-  ASSERT_EQ(original_working_dir, ProjectRootPath());
+  // Change to a subdirectory path.
+  ASSERT_EQ(0, chdir(empty_dummy_dir_.c_str()));
+  ASSERT_EQ(original_working_dir, webrtc::test::ProjectRootPath());
 }
 
 // Similar to the above test, but for the output dir
 TEST_F(FileUtilsTest, OutputPathFromDeeperWorkingDir) {
-  std::string path = OutputPath();
+  std::string path = webrtc::test::OutputPath();
   std::string original_working_dir = path;
-  path += "foo/bar/baz";
-  chdir(path.c_str());
-  ASSERT_EQ(original_working_dir, OutputPath());
+  ASSERT_EQ(0, chdir(empty_dummy_dir_.c_str()));
+  ASSERT_EQ(original_working_dir, webrtc::test::OutputPath());
 }
 
 // Tests with current working directory set to a directory higher up in the
@@ -95,15 +140,43 @@
 TEST_F(FileUtilsTest, ProjectRootPathFromRootWorkingDir) {
   // Change current working dir to the root of the current file system
   // (this will always be "above" our project root dir).
-  chdir(kPathDelimiter);
-  ASSERT_EQ(kCannotFindProjectRootDir, ProjectRootPath());
+  ASSERT_EQ(0, chdir(kPathDelimiter));
+  ASSERT_EQ(webrtc::test::kCannotFindProjectRootDir,
+            webrtc::test::ProjectRootPath());
 }
 
 // Similar to the above test, but for the output dir
 TEST_F(FileUtilsTest, OutputPathFromRootWorkingDir) {
-  chdir(kPathDelimiter);
-  ASSERT_EQ("./", OutputPath());
+  ASSERT_EQ(0, chdir(kPathDelimiter));
+  ASSERT_EQ("./", webrtc::test::OutputPath());
 }
 
-}  // namespace test
+// Only tests that the code executes
+TEST_F(FileUtilsTest, CreateDirectory) {
+  std::string directory = "fileutils-unittest-empty-dir";
+  // Make sure it's removed if a previous test has failed:
+  std::remove(directory.c_str());
+  ASSERT_TRUE(webrtc::test::CreateDirectory(directory));
+  std::remove(directory.c_str());
+}
+
+TEST_F(FileUtilsTest, WorkingDirReturnsValue) {
+  // Hard to cover all platforms. Just test that it returns something without
+  // crashing:
+  std::string working_dir = webrtc::test::WorkingDir();
+  ASSERT_TRUE(working_dir.length() > 0);
+}
+
+// Due to multiple platforms, it is hard to make a complete test for
+// ResourcePath. Manual testing has been performed by removing files and
+// verified the result confirms with the specified documentation for the
+// function.
+TEST_F(FileUtilsTest, ResourcePathReturnsValue) {
+  std::string resource = webrtc::test::ResourcePath(kTestName, kExtension);
+  ASSERT_TRUE(resource.find(kTestName) > 0);
+  ASSERT_TRUE(resource.find(kExtension) > 0);
+  ASSERT_EQ(0, chdir(kPathDelimiter));
+  ASSERT_EQ("./", webrtc::test::OutputPath());
+}
+
 }  // namespace webrtc