From 8434aeb3a7e489ff0bb206f31d940e2ff5951693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20H=C3=B6glund?= Date: Fri, 5 Oct 2018 14:52:11 +0200 Subject: [PATCH] Use Chromium's code for locating the src dir. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code is much more sophisticated in that it doesn't rely on argv[0], but rather asks the OS where our executable is. We can then simply go two steps up since we count on running in out/Whatever relative to the src dir. This is how Chromium does it. The aim here is to get rid of SetExecutablePath, which will be the next CL. Bug: webrtc:9792 Change-Id: I7da027b7391e759b1f612de12f27a244fe884c09 Reviewed-on: https://webrtc-review.googlesource.com/c/103121 Reviewed-by: Artem Titov Commit-Queue: Patrik Höglund Cr-Commit-Position: refs/heads/master@{#25017} --- test/BUILD.gn | 21 +++- test/testsupport/fileutils.cc | 133 +++++++++++++------------ test/testsupport/fileutils.h | 17 +++- test/testsupport/fileutils_unittest.cc | 105 ++++++++++++------- test/testsupport/iosfileutils.h | 26 +++++ test/testsupport/macfileutils.h | 24 +++++ test/testsupport/macfileutils.mm | 43 ++++++++ 7 files changed, 263 insertions(+), 106 deletions(-) create mode 100644 test/testsupport/iosfileutils.h create mode 100644 test/testsupport/macfileutils.h create mode 100644 test/testsupport/macfileutils.mm diff --git a/test/BUILD.gn b/test/BUILD.gn index c7f7383c21..28146e0599 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -359,9 +359,10 @@ if (rtc_include_tests) { } if (is_ios) { - rtc_source_set("fileutils_objc") { + rtc_source_set("fileutils_ios_objc") { visibility = [ ":fileutils" ] sources = [ + "testsupport/iosfileutils.h", "testsupport/iosfileutils.mm", ] deps = [ @@ -373,6 +374,19 @@ if (is_ios) { } } +if (is_mac) { + rtc_source_set("fileutils_mac_objc") { + visibility = [ ":fileutils" ] + sources = [ + "testsupport/macfileutils.h", + "testsupport/macfileutils.mm", + ] + deps = [ + "../rtc_base:checks", + ] + } +} + rtc_source_set("fileutils") { testonly = true visibility = [ "*" ] @@ -388,7 +402,10 @@ rtc_source_set("fileutils") { "//third_party/abseil-cpp/absl/types:optional", ] if (is_ios) { - deps += [ ":fileutils_objc" ] + deps += [ ":fileutils_ios_objc" ] + } + if (is_mac) { + deps += [ ":fileutils_mac_objc" ] } if (is_win) { deps += [ "../rtc_base:rtc_base" ] diff --git a/test/testsupport/fileutils.cc b/test/testsupport/fileutils.cc index 370ee06b29..cb88472e42 100644 --- a/test/testsupport/fileutils.cc +++ b/test/testsupport/fileutils.cc @@ -12,11 +12,17 @@ #include -#ifdef WIN32 +#if defined(WEBRTC_POSIX) +#include +#endif + +#if defined(WEBRTC_WIN) #include #include #include #include +#include +#include #include "Shlwapi.h" #include "WinDef.h" @@ -42,35 +48,34 @@ #include #include +#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 { -#if defined(WEBRTC_IOS) -// Defined in iosfileutils.mm. No header file to discourage use elsewhere. -std::string IOSOutputPath(); -std::string IOSRootPath(); -std::string IOSResourcePath(std::string name, std::string extension); -#endif - namespace { -#ifdef WIN32 +#if defined(WEBRTC_WIN) const char* kPathDelimiter = "\\"; #else const char* kPathDelimiter = "/"; #endif -#ifdef WEBRTC_ANDROID -const char* kRootDirName = "/sdcard/chromium_tests_root/"; -#else -#if !defined(WEBRTC_IOS) -const char* kOutputDirName = "out"; +#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 -const char* kFallbackPath = "./"; -#endif // !defined(WEBRTC_ANDROID) #if !defined(WEBRTC_IOS) const char* kResourcesDirName = "resources"; @@ -83,6 +88,19 @@ bool relative_dir_path_set = false; const char* kCannotFindProjectRootDir = "ERROR_CANNOT_FIND_PROJECT_ROOT_DIR"; +std::string DirName(const std::string& path) { + if (path.empty()) + return ""; + if (path == kPathDelimiter) + return path; + + std::string result = path; + if (result.back() == *kPathDelimiter) + result.pop_back(); // Remove trailing separator. + + return result.substr(0, result.find_last_of(kPathDelimiter)); +} + void SetExecutablePath(const std::string& path) { std::string working_dir = WorkingDir(); std::string temp_path = path; @@ -99,7 +117,7 @@ void SetExecutablePath(const std::string& path) { #endif // Trim away the executable name; only store the relative dir path. - temp_path = temp_path.substr(0, temp_path.find_last_of(kPathDelimiter)); + temp_path = DirName(temp_path); strncpy(relative_dir_path, temp_path.c_str(), FILENAME_MAX); relative_dir_path_set = true; } @@ -115,78 +133,71 @@ bool DirExists(const std::string& directory_name) { S_ISDIR(directory_info.st_mode); } -#ifdef WEBRTC_ANDROID - +// Finds the WebRTC src dir. +// The returned path always ends with a path separator. std::string ProjectRootPath() { - return kRootDirName; -} - -std::string OutputPath() { - return kRootDirName; -} - -std::string WorkingDir() { - return kRootDirName; -} - -#else // WEBRTC_ANDROID - -std::string ProjectRootPath() { -#if defined(WEBRTC_IOS) +#if defined(WEBRTC_ANDROID) + return kAndroidChromiumTestsRoot; +#elif defined WEBRTC_IOS return IOSRootPath(); -#else - std::string path = WorkingDir(); - if (path == kFallbackPath) { +#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; } - if (relative_dir_path_set) { - path = path + kPathDelimiter + relative_dir_path; - } - path = path + kPathDelimiter + ".." + kPathDelimiter + ".."; - char canonical_path[FILENAME_MAX]; -#ifdef WIN32 - BOOL succeeded = PathCanonicalizeA(canonical_path, path.c_str()); -#else - bool succeeded = realpath(path.c_str(), canonical_path) != NULL; -#endif - if (succeeded) { - path = std::string(canonical_path) + kPathDelimiter; - return path; - } else { - fprintf(stderr, "Cannot find project root directory!\n"); + // 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(); - if (path == kCannotFindProjectRootDir) { - return kFallbackPath; - } - path += kOutputDirName; + RTC_DCHECK_NE(path, kCannotFindProjectRootDir); + path += "out"; if (!CreateDir(path)) { - return kFallbackPath; + 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 kFallbackPath; + return "./"; } else { return std::string(path_buffer); } } -#endif // !WEBRTC_ANDROID - // Generate a temporary filename in a safe way. // Largely copied from talk/base/{unixfilesystem,win32filesystem}.cc. std::string TempFilename(const std::string& dir, const std::string& prefix) { diff --git a/test/testsupport/fileutils.h b/test/testsupport/fileutils.h index 8cef0e36b5..5ff019600e 100644 --- a/test/testsupport/fileutils.h +++ b/test/testsupport/fileutils.h @@ -25,11 +25,14 @@ namespace test { // to find the project root. extern const char* kCannotFindProjectRootDir; -// Creates and 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 top-level of the project, i.e. a subfolder to -// the path returned by ProjectRootPath(). The exception is Android where we use -// /sdcard/ instead. +// 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. @@ -100,6 +103,9 @@ bool FileExists(const std::string& file_name); // Checks if a directory exists. bool DirExists(const std::string& directory_name); +// Strips the rightmost path segment from a path. +std::string DirName(const std::string& path); + // File size of the supplied file in bytes. Will return 0 if the file is // empty or if the file does not exist/is readable. size_t GetFileSize(const std::string& filename); @@ -110,6 +116,7 @@ size_t GetFileSize(const std::string& filename); // the argv[0] being sent into the main function to make it possible for // fileutils.h to find the correct project paths even when the working directory // is outside the project tree (which happens in some cases). +// TODO(bugs.webrtc.org/9792): Deprecated - going away soon. void SetExecutablePath(const std::string& path_to_executable); } // namespace test diff --git a/test/testsupport/fileutils_unittest.cc b/test/testsupport/fileutils_unittest.cc index dc1f1bc7f7..c6dc86deb2 100644 --- a/test/testsupport/fileutils_unittest.cc +++ b/test/testsupport/fileutils_unittest.cc @@ -19,6 +19,7 @@ #include "absl/types/optional.h" #include "rtc_base/checks.h" +#include "test/gmock.h" #include "test/gtest.h" #ifdef WIN32 @@ -28,14 +29,19 @@ static const char* kPathDelimiter = "\\"; static const char* kPathDelimiter = "/"; #endif -static const char kTestName[] = "fileutils_unittest"; -static const char kExtension[] = "tmp"; +using ::testing::EndsWith; namespace webrtc { namespace test { namespace { +std::string Path(const std::string& path) { + std::string result = path; + std::replace(result.begin(), result.end(), '/', *kPathDelimiter); + return result; +} + // Remove files and directories in a directory non-recursively and writes the // number of deleted items in |num_deleted_entries|. void CleanDir(const std::string& dir, size_t* num_deleted_entries) { @@ -86,34 +92,34 @@ class FileUtilsTest : public testing::Test { std::string FileUtilsTest::original_working_dir_ = ""; -// Tests that the project output dir 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. -#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) -#define MAYBE_OutputPathFromUnchangedWorkingDir \ - DISABLED_OutputPathFromUnchangedWorkingDir +// The location will vary depending on where the webrtc checkout is on the +// system, but it should end as described above and be an absolute path. +std::string ExpectedRootDirByPlatform() { +#if defined(WEBRTC_ANDROID) + return Path("chromium_tests_root/"); +#elif defined(WEBRTC_IOS) + return Path("tmp/"); #else -#define MAYBE_OutputPathFromUnchangedWorkingDir \ - OutputPathFromUnchangedWorkingDir + return Path("out/"); #endif -TEST_F(FileUtilsTest, MAYBE_OutputPathFromUnchangedWorkingDir) { - 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)); +} + +TEST_F(FileUtilsTest, OutputPathFromUnchangedWorkingDir) { + std::string expected_end = ExpectedRootDirByPlatform(); + std::string result = webrtc::test::OutputPath(); + + ASSERT_THAT(result, EndsWith(expected_end)); } // Tests with current working directory set to a directory higher up in the // directory tree than the project root dir. -#if defined(WEBRTC_ANDROID) || defined(WIN32) || defined(WEBRTC_IOS) -#define MAYBE_OutputPathFromRootWorkingDir DISABLED_OutputPathFromRootWorkingDir -#else -#define MAYBE_OutputPathFromRootWorkingDir OutputPathFromRootWorkingDir -#endif -TEST_F(FileUtilsTest, MAYBE_OutputPathFromRootWorkingDir) { +TEST_F(FileUtilsTest, OutputPathFromRootWorkingDir) { ASSERT_EQ(0, chdir(kPathDelimiter)); - ASSERT_EQ("./", webrtc::test::OutputPath()); + + std::string expected_end = ExpectedRootDirByPlatform(); + std::string result = webrtc::test::OutputPath(); + + ASSERT_THAT(result, EndsWith(expected_end)); } TEST_F(FileUtilsTest, TempFilename) { @@ -152,30 +158,37 @@ TEST_F(FileUtilsTest, MAYBE_CreateDir) { } TEST_F(FileUtilsTest, WorkingDirReturnsValue) { - // Hard to cover all platforms. Just test that it returns something without - // crashing: + // This will obviously be different depending on where the webrtc checkout is, + // so just check something is returned. std::string working_dir = webrtc::test::WorkingDir(); ASSERT_GT(working_dir.length(), 0u); } -// 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_GT(resource.find(kTestName), 0u); - ASSERT_GT(resource.find(kExtension), 0u); +TEST_F(FileUtilsTest, ResourcePathReturnsCorrectPath) { + std::string result = webrtc::test::ResourcePath( + Path("video_coding/frame-ethernet-ii"), "pcap"); +#if defined(WEBRTC_IOS) + // iOS bundles resources straight into the bundle root. + std::string expected_end = Path("/frame-ethernet-ii.pcap"); +#else + // Other platforms: it's a separate dir. + std::string expected_end = + Path("resources/video_coding/frame-ethernet-ii.pcap"); +#endif + + ASSERT_THAT(result, EndsWith(expected_end)); + ASSERT_TRUE(FileExists(result)) << "Expected " << result << " to exist; did " + << "ResourcePath return an incorrect path?"; } TEST_F(FileUtilsTest, ResourcePathFromRootWorkingDir) { ASSERT_EQ(0, chdir(kPathDelimiter)); - std::string resource = webrtc::test::ResourcePath(kTestName, kExtension); + std::string resource = webrtc::test::ResourcePath("whatever", "ext"); #if !defined(WEBRTC_IOS) ASSERT_NE(resource.find("resources"), std::string::npos); #endif - ASSERT_GT(resource.find(kTestName), 0u); - ASSERT_GT(resource.find(kExtension), 0u); + ASSERT_GT(resource.find("whatever"), 0u); + ASSERT_GT(resource.find("ext"), 0u); } TEST_F(FileUtilsTest, GetFileSizeExistingFile) { @@ -220,7 +233,7 @@ TEST_F(FileUtilsTest, WriteReadDeleteFilesAndDirs) { // Create an empty temporary directory for this test. const std::string temp_directory = - OutputPath() + "TempFileUtilsTestReadDirectory" + kPathDelimiter; + OutputPath() + Path("TempFileUtilsTestReadDirectory/"); CreateDir(temp_directory); EXPECT_NO_FATAL_FAILURE(CleanDir(temp_directory, &num_deleted_entries)); EXPECT_TRUE(DirExists(temp_directory)); @@ -231,7 +244,7 @@ TEST_F(FileUtilsTest, WriteReadDeleteFilesAndDirs) { EXPECT_TRUE(FileExists(temp_filename)); // Add an empty directory. - const std::string temp_subdir = temp_directory + "subdir" + kPathDelimiter; + const std::string temp_subdir = temp_directory + Path("subdir/"); EXPECT_TRUE(CreateDir(temp_subdir)); EXPECT_TRUE(DirExists(temp_subdir)); @@ -246,5 +259,21 @@ TEST_F(FileUtilsTest, WriteReadDeleteFilesAndDirs) { EXPECT_FALSE(DirExists(temp_directory)); } +TEST_F(FileUtilsTest, DirNameStripsFilename) { + EXPECT_EQ(Path("/some/path"), DirName(Path("/some/path/file.txt"))); +} + +TEST_F(FileUtilsTest, DirNameKeepsStrippingRightmostPathComponent) { + EXPECT_EQ(Path("/some"), DirName(DirName(Path("/some/path/file.txt")))); +} + +TEST_F(FileUtilsTest, DirNameDoesntCareIfAPathEndsInPathSeparator) { + EXPECT_EQ(Path("/some"), DirName(Path("/some/path/"))); +} + +TEST_F(FileUtilsTest, DirNameStopsAtRoot) { + EXPECT_EQ(Path("/"), DirName(Path("/"))); +} + } // namespace test } // namespace webrtc diff --git a/test/testsupport/iosfileutils.h b/test/testsupport/iosfileutils.h new file mode 100644 index 0000000000..f0194ba82a --- /dev/null +++ b/test/testsupport/iosfileutils.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#ifndef TEST_TESTSUPPORT_IOSFILEUTILS_H_ +#define TEST_TESTSUPPORT_IOSFILEUTILS_H_ + +#include + +namespace webrtc { +namespace test { + +std::string IOSOutputPath(); +std::string IOSRootPath(); +std::string IOSResourcePath(std::string name, std::string extension); + +} // namespace test +} // namespace webrtc + +#endif // TEST_TESTSUPPORT_IOSFILEUTILS_H_ diff --git a/test/testsupport/macfileutils.h b/test/testsupport/macfileutils.h new file mode 100644 index 0000000000..82ae866efb --- /dev/null +++ b/test/testsupport/macfileutils.h @@ -0,0 +1,24 @@ +/* + * 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. + */ + +#ifndef TEST_TESTSUPPORT_MACFILEUTILS_H_ +#define TEST_TESTSUPPORT_MACFILEUTILS_H_ + +#include + +namespace webrtc { +namespace test { + +void GetNSExecutablePath(std::string* path); + +} // namespace test +} // namespace webrtc + +#endif // TEST_TESTSUPPORT_MACFILEUTILS_H_ diff --git a/test/testsupport/macfileutils.mm b/test/testsupport/macfileutils.mm new file mode 100644 index 0000000000..270ecbc9a5 --- /dev/null +++ b/test/testsupport/macfileutils.mm @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#import +#include +#include +#include +#include + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +void GetNSExecutablePath(std::string* path) { + RTC_DCHECK(path); + // Executable path can have relative references ("..") depending on + // how the app was launched. + uint32_t executable_length = 0; + _NSGetExecutablePath(NULL, &executable_length); + RTC_DCHECK_GT(executable_length, 1u); + char executable_path[PATH_MAX + 1]; + int rv = _NSGetExecutablePath(executable_path, &executable_length); + RTC_DCHECK_EQ(rv, 0); + + char full_path[PATH_MAX]; + if (realpath(executable_path, full_path) == nullptr) { + *path = ""; + return; + } + + *path = full_path; +} + +} // namespace test +} // namespace webrtc