Add ability to override detection of resource location and source root

Bug: webrtc:9792
Change-Id: I944d2e1c1b4b4154a90eba6fbe9c417aad17498d
Reviewed-on: https://webrtc-review.googlesource.com/c/107401
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25319}
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 4d294cd..dc6dc79 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -377,7 +377,10 @@
 
 if (is_ios) {
   rtc_source_set("fileutils_ios_objc") {
-    visibility = [ ":fileutils" ]
+    visibility = [
+      ":fileutils",
+      ":fileutils_override_impl",
+    ]
     sources = [
       "testsupport/iosfileutils.h",
       "testsupport/iosfileutils.mm",
@@ -393,7 +396,10 @@
 
 if (is_mac) {
   rtc_source_set("fileutils_mac_objc") {
-    visibility = [ ":fileutils" ]
+    visibility = [
+      ":fileutils",
+      ":fileutils_override_impl",
+    ]
     sources = [
       "testsupport/macfileutils.h",
       "testsupport/macfileutils.mm",
@@ -412,6 +418,42 @@
     "testsupport/fileutils.h",
   ]
   deps = [
+    ":fileutils_override_api",
+    ":fileutils_override_impl",
+    "..:webrtc_common",
+    "../rtc_base:checks",
+    "../rtc_base:rtc_base_approved",
+    "../rtc_base/system:arch",
+    "//third_party/abseil-cpp/absl/types:optional",
+  ]
+  if (is_ios) {
+    deps += [ ":fileutils_ios_objc" ]
+  }
+  if (is_mac) {
+    deps += [ ":fileutils_mac_objc" ]
+  }
+  if (is_win) {
+    deps += [ "../rtc_base:rtc_base" ]
+  }
+}
+
+# We separate header into own target to make it possible for downstream
+# projects to override implementation.
+rtc_source_set("fileutils_override_api") {
+  testonly = true
+  sources = [
+    "testsupport/fileutils_override.h",
+  ]
+}
+
+rtc_source_set("fileutils_override_impl") {
+  testonly = true
+  visibility = [ ":fileutils" ]
+  sources = [
+    "testsupport/fileutils_override.cc",
+  ]
+  deps = [
+    ":fileutils_override_api",
     "..:webrtc_common",
     "../rtc_base:checks",
     "../rtc_base:rtc_base_approved",
diff --git a/test/testsupport/fileutils.cc b/test/testsupport/fileutils.cc
index ba1e793..0eac8d5 100644
--- a/test/testsupport/fileutils.cc
+++ b/test/testsupport/fileutils.cc
@@ -59,6 +59,7 @@
 #include "rtc_base/arraysize.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/stringutils.h"
+#include "test/testsupport/fileutils_override.h"
 
 namespace webrtc {
 namespace test {
@@ -71,20 +72,8 @@
 const char* kPathDelimiter = "/";
 #endif
 
-#if defined(WEBRTC_ANDROID)
-// This is a special case in Chrome infrastructure. See
-// base/test/test_support_android.cc.
-const char* kAndroidChromiumTestsRoot = "/sdcard/chromium_tests_root/";
-#endif
-
-#if !defined(WEBRTC_IOS)
-const char* kResourcesDirName = "resources";
-#endif
-
 }  // namespace
 
-const char* kCannotFindProjectRootDir = "ERROR_CANNOT_FIND_PROJECT_ROOT_DIR";
-
 std::string DirName(const std::string& path) {
   if (path.empty())
     return "";
@@ -109,69 +98,12 @@
          S_ISDIR(directory_info.st_mode);
 }
 
-// Finds the WebRTC src dir.
-// The returned path always ends with a path separator.
-std::string ProjectRootPath() {
-#if defined(WEBRTC_ANDROID)
-  return kAndroidChromiumTestsRoot;
-#elif defined WEBRTC_IOS
-  return IOSRootPath();
-#elif defined(WEBRTC_MAC)
-  std::string path;
-  GetNSExecutablePath(&path);
-  std::string exe_dir = DirName(path);
-  // On Mac, tests execute in out/Whatever, so src is two levels up except if
-  // the test is bundled (which our tests are not), in which case it's 5 levels.
-  return DirName(DirName(exe_dir)) + kPathDelimiter;
-#elif defined(WEBRTC_POSIX)
-  char buf[PATH_MAX];
-  ssize_t count = ::readlink("/proc/self/exe", buf, arraysize(buf));
-  if (count <= 0) {
-    RTC_NOTREACHED() << "Unable to resolve /proc/self/exe.";
-    return kCannotFindProjectRootDir;
-  }
-  // On POSIX, tests execute in out/Whatever, so src is two levels up.
-  std::string exe_dir = DirName(std::string(buf, count));
-  return DirName(DirName(exe_dir)) + kPathDelimiter;
-#elif defined(WEBRTC_WIN)
-  wchar_t buf[MAX_PATH];
-  buf[0] = 0;
-  if (GetModuleFileName(NULL, buf, MAX_PATH) == 0)
-    return kCannotFindProjectRootDir;
-
-  std::string exe_path = rtc::ToUtf8(std::wstring(buf));
-  std::string exe_dir = DirName(exe_path);
-  return DirName(DirName(exe_dir)) + kPathDelimiter;
-#endif
-}
-
 std::string OutputPath() {
-#if defined(WEBRTC_IOS)
-  return IOSOutputPath();
-#elif defined(WEBRTC_ANDROID)
-  return kAndroidChromiumTestsRoot;
-#else
-  std::string path = ProjectRootPath();
-  RTC_DCHECK_NE(path, kCannotFindProjectRootDir);
-  path += "out";
-  if (!CreateDir(path)) {
-    return "./";
-  }
-  return path + kPathDelimiter;
-#endif
+  return webrtc::test::internal::OutputPath();
 }
 
 std::string WorkingDir() {
-#if defined(WEBRTC_ANDROID)
-  return kAndroidChromiumTestsRoot;
-#endif
-  char path_buffer[FILENAME_MAX];
-  if (!GET_CURRENT_DIR(path_buffer, sizeof(path_buffer))) {
-    fprintf(stderr, "Cannot get current directory!\n");
-    return "./";
-  } else {
-    return std::string(path_buffer);
-  }
+  return webrtc::test::internal::WorkingDir();
 }
 
 // Generate a temporary filename in a safe way.
@@ -298,13 +230,7 @@
 
 std::string ResourcePath(const std::string& name,
                          const std::string& extension) {
-#if defined(WEBRTC_IOS)
-  return IOSResourcePath(name, extension);
-#else
-  std::string resources_path =
-      ProjectRootPath() + kResourcesDirName + kPathDelimiter;
-  return resources_path + name + "." + extension;
-#endif
+  return webrtc::test::internal::ResourcePath(name, extension);
 }
 
 std::string JoinFilename(const std::string& dir, const std::string& name) {
diff --git a/test/testsupport/fileutils_override.cc b/test/testsupport/fileutils_override.cc
new file mode 100644
index 0000000..f29ddb8
--- /dev/null
+++ b/test/testsupport/fileutils_override.cc
@@ -0,0 +1,151 @@
+/*
+ *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "test/testsupport/fileutils_override.h"
+
+#if defined(WEBRTC_WIN)
+#include <direct.h>
+#include <tchar.h>
+#include <windows.h>
+#include <algorithm>
+#include <codecvt>
+#include <locale>
+
+#include "Shlwapi.h"
+#include "WinDef.h"
+
+#include "rtc_base/win32.h"
+#define GET_CURRENT_DIR _getcwd
+#else
+#include <dirent.h>
+#include <unistd.h>
+#define GET_CURRENT_DIR getcwd
+#endif
+
+#if defined(WEBRTC_IOS)
+#include "test/testsupport/iosfileutils.h"
+#endif
+
+#if defined(WEBRTC_MAC)
+#include "test/testsupport/macfileutils.h"
+#endif
+
+#include "rtc_base/arraysize.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/stringutils.h"
+
+namespace webrtc {
+namespace test {
+
+std::string DirName(const std::string& path);
+bool CreateDir(const std::string& directory_name);
+
+namespace internal {
+
+namespace {
+#if defined(WEBRTC_WIN)
+const char* kPathDelimiter = "\\";
+#elif !defined(WEBRTC_IOS)
+const char* kPathDelimiter = "/";
+#endif
+
+#if defined(WEBRTC_ANDROID)
+// This is a special case in Chrome infrastructure. See
+// base/test/test_support_android.cc.
+const char* kAndroidChromiumTestsRoot = "/sdcard/chromium_tests_root/";
+#endif
+
+#if !defined(WEBRTC_IOS)
+const char* kResourcesDirName = "resources";
+#endif
+
+}  // namespace
+
+const char* kCannotFindProjectRootDir = "ERROR_CANNOT_FIND_PROJECT_ROOT_DIR";
+
+// Finds the WebRTC src dir.
+// The returned path always ends with a path separator.
+std::string ProjectRootPath() {
+#if defined(WEBRTC_ANDROID)
+  return kAndroidChromiumTestsRoot;
+#elif defined WEBRTC_IOS
+  return IOSRootPath();
+#elif defined(WEBRTC_MAC)
+  std::string path;
+  GetNSExecutablePath(&path);
+  std::string exe_dir = DirName(path);
+  // On Mac, tests execute in out/Whatever, so src is two levels up except if
+  // the test is bundled (which our tests are not), in which case it's 5 levels.
+  return DirName(DirName(exe_dir)) + kPathDelimiter;
+#elif defined(WEBRTC_POSIX)
+  char buf[PATH_MAX];
+  ssize_t count = ::readlink("/proc/self/exe", buf, arraysize(buf));
+  if (count <= 0) {
+    RTC_NOTREACHED() << "Unable to resolve /proc/self/exe.";
+    return kCannotFindProjectRootDir;
+  }
+  // On POSIX, tests execute in out/Whatever, so src is two levels up.
+  std::string exe_dir = DirName(std::string(buf, count));
+  return DirName(DirName(exe_dir)) + kPathDelimiter;
+#elif defined(WEBRTC_WIN)
+  wchar_t buf[MAX_PATH];
+  buf[0] = 0;
+  if (GetModuleFileName(NULL, buf, MAX_PATH) == 0)
+    return kCannotFindProjectRootDir;
+
+  std::string exe_path = rtc::ToUtf8(std::wstring(buf));
+  std::string exe_dir = DirName(exe_path);
+  return DirName(DirName(exe_dir)) + kPathDelimiter;
+#endif
+}
+
+std::string OutputPath() {
+#if defined(WEBRTC_IOS)
+  return IOSOutputPath();
+#elif defined(WEBRTC_ANDROID)
+  return kAndroidChromiumTestsRoot;
+#else
+  std::string path = ProjectRootPath();
+  RTC_DCHECK_NE(path, kCannotFindProjectRootDir);
+  path += "out";
+  if (!CreateDir(path)) {
+    return "./";
+  }
+  return path + kPathDelimiter;
+#endif
+}
+
+std::string WorkingDir() {
+#if defined(WEBRTC_ANDROID)
+  return kAndroidChromiumTestsRoot;
+#endif
+  char path_buffer[FILENAME_MAX];
+  if (!GET_CURRENT_DIR(path_buffer, sizeof(path_buffer))) {
+    fprintf(stderr, "Cannot get current directory!\n");
+    return "./";
+  } else {
+    return std::string(path_buffer);
+  }
+}
+
+std::string ResourcePath(const std::string& name,
+                         const std::string& extension) {
+#if defined(WEBRTC_IOS)
+  return IOSResourcePath(name, extension);
+#else
+  std::string resources_path =
+      ProjectRootPath() + kResourcesDirName + kPathDelimiter;
+  return resources_path + name + "." + extension;
+#endif
+}
+
+}  // namespace internal
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/testsupport/fileutils_override.h b/test/testsupport/fileutils_override.h
new file mode 100644
index 0000000..141c768
--- /dev/null
+++ b/test/testsupport/fileutils_override.h
@@ -0,0 +1,57 @@
+/*
+ *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <stdio.h>
+#include <string>
+
+#ifndef TEST_TESTSUPPORT_FILEUTILS_OVERRIDE_H_
+#define TEST_TESTSUPPORT_FILEUTILS_OVERRIDE_H_
+
+namespace webrtc {
+namespace test {
+namespace internal {
+
+// Returns the absolute path to the output directory where log files and other
+// test artifacts should be put. The output directory is generally a directory
+// named "out" at the project root. This root is assumed to be two levels above
+// where the test binary is located; this is because tests execute in a dir
+// out/Whatever relative to the project root. This convention is also followed
+// in Chromium.
+//
+// The exception is Android where we use /sdcard/ instead.
+//
+// If symbolic links occur in the path they will be resolved and the actual
+// directory will be returned.
+//
+// Returns the path WITH a trailing path delimiter. If the project root is not
+// found, the current working directory ("./") is returned as a fallback.
+std::string OutputPath();
+
+// 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();
+
+// Returns a path to a resource file in [project-root]/resources/ dir.
+// Returns an absolute path
+//
+// 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(const std::string& name, const std::string& extension);
+
+}  // namespace internal
+}  // namespace test
+}  // namespace webrtc
+
+#endif  // TEST_TESTSUPPORT_FILEUTILS_OVERRIDE_H_