1 //
2 // Copyright 2019 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 // system_utils_unittest.cpp: Unit tests for ANGLE's system utility functions
7
8 #include "gmock/gmock.h"
9 #include "gtest/gtest.h"
10
11 #include "common/mathutil.h"
12 #include "common/system_utils.h"
13 #include "util/test_utils.h"
14
15 #include <fstream>
16 #include <sstream>
17 #include <vector>
18
19 #if defined(ANGLE_PLATFORM_POSIX)
20 # include <signal.h>
21 #endif
22
23 using namespace angle;
24
25 namespace
26 {
27 // Test getting the executable path
TEST(SystemUtils,ExecutablePath)28 TEST(SystemUtils, ExecutablePath)
29 {
30 // TODO: fuchsia support. http://anglebug.com/42261836
31 #if !defined(ANGLE_PLATFORM_FUCHSIA)
32 std::string executablePath = GetExecutablePath();
33 EXPECT_NE("", executablePath);
34 #endif
35 }
36
37 // Test getting the executable directory
TEST(SystemUtils,ExecutableDir)38 TEST(SystemUtils, ExecutableDir)
39 {
40 // TODO: fuchsia support. http://anglebug.com/42261836
41 #if !defined(ANGLE_PLATFORM_FUCHSIA)
42 std::string executableDir = GetExecutableDirectory();
43 EXPECT_NE("", executableDir);
44
45 std::string executablePath = GetExecutablePath();
46 EXPECT_LT(executableDir.size(), executablePath.size());
47 EXPECT_EQ(0, strncmp(executableDir.c_str(), executablePath.c_str(), executableDir.size()));
48 #endif
49 }
50
51 // Test setting environment variables
TEST(SystemUtils,Environment)52 TEST(SystemUtils, Environment)
53 {
54 constexpr char kEnvVarName[] = "UNITTEST_ENV_VARIABLE";
55 constexpr char kEnvVarValue[] = "The quick brown fox jumps over the lazy dog";
56
57 bool setEnvDone = SetEnvironmentVar(kEnvVarName, kEnvVarValue);
58 EXPECT_TRUE(setEnvDone);
59
60 std::string readback = GetEnvironmentVar(kEnvVarName);
61 EXPECT_EQ(kEnvVarValue, readback);
62
63 bool unsetEnvDone = UnsetEnvironmentVar(kEnvVarName);
64 EXPECT_TRUE(unsetEnvDone);
65
66 readback = GetEnvironmentVar(kEnvVarName);
67 EXPECT_EQ("", readback);
68 }
69
70 // Test CPU time measurement with a small operation
71 // (pretty much the measurement itself)
TEST(SystemUtils,CpuTimeSmallOp)72 TEST(SystemUtils, CpuTimeSmallOp)
73 {
74 double cpuTimeStart = GetCurrentProcessCpuTime();
75 double cpuTimeEnd = GetCurrentProcessCpuTime();
76 EXPECT_GE(cpuTimeEnd, cpuTimeStart);
77 }
78
79 // Test CPU time measurement with a sleepy operation
TEST(SystemUtils,CpuTimeSleepy)80 TEST(SystemUtils, CpuTimeSleepy)
81 {
82 double cpuTimeStart = GetCurrentProcessCpuTime();
83 angle::Sleep(1);
84 double cpuTimeEnd = GetCurrentProcessCpuTime();
85 EXPECT_GE(cpuTimeEnd, cpuTimeStart);
86 }
87
88 // Test CPU time measurement with a heavy operation
TEST(SystemUtils,CpuTimeHeavyOp)89 TEST(SystemUtils, CpuTimeHeavyOp)
90 {
91 constexpr size_t bufferSize = 1048576;
92 std::vector<uint8_t> buffer(bufferSize, 1);
93 double cpuTimeStart = GetCurrentProcessCpuTime();
94 memset(buffer.data(), 0, bufferSize);
95 double cpuTimeEnd = GetCurrentProcessCpuTime();
96 EXPECT_GE(cpuTimeEnd, cpuTimeStart);
97 }
98
99 #if defined(ANGLE_PLATFORM_POSIX)
TEST(SystemUtils,ConcatenatePathSimple)100 TEST(SystemUtils, ConcatenatePathSimple)
101 {
102 std::string path1 = "/this/is/path1";
103 std::string path2 = "this/is/path2";
104 std::string expected = "/this/is/path1/this/is/path2";
105 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
106 }
107
TEST(SystemUtils,ConcatenatePath1Empty)108 TEST(SystemUtils, ConcatenatePath1Empty)
109 {
110 std::string path1 = "";
111 std::string path2 = "this/is/path2";
112 std::string expected = "this/is/path2";
113 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
114 }
115
TEST(SystemUtils,ConcatenatePath2Empty)116 TEST(SystemUtils, ConcatenatePath2Empty)
117 {
118 std::string path1 = "/this/is/path1";
119 std::string path2 = "";
120 std::string expected = "/this/is/path1";
121 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
122 }
123
TEST(SystemUtils,ConcatenatePath2FullPath)124 TEST(SystemUtils, ConcatenatePath2FullPath)
125 {
126 std::string path1 = "/this/is/path1";
127 std::string path2 = "/this/is/path2";
128 std::string expected = "/this/is/path2";
129 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
130 }
131
TEST(SystemUtils,ConcatenatePathRedundantSeparators)132 TEST(SystemUtils, ConcatenatePathRedundantSeparators)
133 {
134 std::string path1 = "/this/is/path1/";
135 std::string path2 = "this/is/path2";
136 std::string expected = "/this/is/path1/this/is/path2";
137 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
138 }
139
TEST(SystemUtils,IsFullPath)140 TEST(SystemUtils, IsFullPath)
141 {
142 std::string path1 = "/this/is/path1/";
143 std::string path2 = "this/is/path2";
144 EXPECT_TRUE(IsFullPath(path1));
145 EXPECT_FALSE(IsFullPath(path2));
146 }
147 #elif defined(ANGLE_PLATFORM_WINDOWS)
TEST(SystemUtils,ConcatenatePathSimple)148 TEST(SystemUtils, ConcatenatePathSimple)
149 {
150 std::string path1 = "C:\\this\\is\\path1";
151 std::string path2 = "this\\is\\path2";
152 std::string expected = "C:\\this\\is\\path1\\this\\is\\path2";
153 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
154 }
155
TEST(SystemUtils,ConcatenatePath1Empty)156 TEST(SystemUtils, ConcatenatePath1Empty)
157 {
158 std::string path1 = "";
159 std::string path2 = "this\\is\\path2";
160 std::string expected = "this\\is\\path2";
161 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
162 }
163
TEST(SystemUtils,ConcatenatePath2Empty)164 TEST(SystemUtils, ConcatenatePath2Empty)
165 {
166 std::string path1 = "C:\\this\\is\\path1";
167 std::string path2 = "";
168 std::string expected = "C:\\this\\is\\path1";
169 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
170 }
171
TEST(SystemUtils,ConcatenatePath2FullPath)172 TEST(SystemUtils, ConcatenatePath2FullPath)
173 {
174 std::string path1 = "C:\\this\\is\\path1";
175 std::string path2 = "C:\\this\\is\\path2";
176 std::string expected = "C:\\this\\is\\path2";
177 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
178 }
179
TEST(SystemUtils,ConcatenatePathRedundantSeparators)180 TEST(SystemUtils, ConcatenatePathRedundantSeparators)
181 {
182 std::string path1 = "C:\\this\\is\\path1\\";
183 std::string path2 = "this\\is\\path2";
184 std::string expected = "C:\\this\\is\\path1\\this\\is\\path2";
185 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
186 }
187
TEST(SystemUtils,ConcatenatePathRedundantSeparators2)188 TEST(SystemUtils, ConcatenatePathRedundantSeparators2)
189 {
190 std::string path1 = "C:\\this\\is\\path1\\";
191 std::string path2 = "\\this\\is\\path2";
192 std::string expected = "C:\\this\\is\\path1\\this\\is\\path2";
193 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
194 }
195
TEST(SystemUtils,ConcatenatePathRedundantSeparators3)196 TEST(SystemUtils, ConcatenatePathRedundantSeparators3)
197 {
198 std::string path1 = "C:\\this\\is\\path1";
199 std::string path2 = "\\this\\is\\path2";
200 std::string expected = "C:\\this\\is\\path1\\this\\is\\path2";
201 EXPECT_EQ(ConcatenatePath(path1, path2), expected);
202 }
203
TEST(SystemUtils,IsFullPath)204 TEST(SystemUtils, IsFullPath)
205 {
206 std::string path1 = "C:\\this\\is\\path1\\";
207 std::string path2 = "this\\is\\path2";
208 EXPECT_TRUE(IsFullPath(path1));
209 EXPECT_FALSE(IsFullPath(path2));
210 }
211 #endif
212
213 // Temporary file creation is not supported on Android right now.
214 #if defined(ANGLE_PLATFORM_ANDROID)
215 # define MAYBE_CreateAndDeleteTemporaryFile DISABLED_CreateAndDeleteTemporaryFile
216 # define MAYBE_CreateAndDeleteFileInTempDir DISABLED_CreateAndDeleteFileInTempDir
217 #else
218 # define MAYBE_CreateAndDeleteTemporaryFile CreateAndDeleteTemporaryFile
219 # define MAYBE_CreateAndDeleteFileInTempDir CreateAndDeleteFileInTempDir
220 #endif // defined(ANGLE_PLATFORM_ANDROID)
221
222 // Test creating/using temporary file
TEST(SystemUtils,MAYBE_CreateAndDeleteTemporaryFile)223 TEST(SystemUtils, MAYBE_CreateAndDeleteTemporaryFile)
224 {
225 Optional<std::string> path = CreateTemporaryFile();
226 ASSERT_TRUE(path.valid());
227 ASSERT_TRUE(!path.value().empty());
228
229 const std::string testContents = "test output";
230
231 // Test writing
232 std::ofstream out;
233 out.open(path.value());
234 ASSERT_TRUE(out.is_open());
235 out << testContents;
236 EXPECT_TRUE(out.good());
237 out.close();
238
239 // Test reading
240 std::ifstream in;
241 in.open(path.value());
242 EXPECT_TRUE(in.is_open());
243 std::ostringstream sstr;
244 sstr << in.rdbuf();
245 EXPECT_EQ(sstr.str(), testContents);
246 in.close();
247
248 // Test deleting
249 EXPECT_TRUE(DeleteSystemFile(path.value().c_str()));
250 }
251
252 // Test creating/using file created in system's temporary directory
TEST(SystemUtils,MAYBE_CreateAndDeleteFileInTempDir)253 TEST(SystemUtils, MAYBE_CreateAndDeleteFileInTempDir)
254 {
255 Optional<std::string> tempDir = GetTempDirectory();
256 ASSERT_TRUE(tempDir.valid());
257
258 Optional<std::string> path = CreateTemporaryFileInDirectory(tempDir.value());
259 ASSERT_TRUE(path.valid());
260 ASSERT_TRUE(!path.value().empty());
261
262 const std::string testContents = "test output";
263
264 // Test writing
265 std::ofstream out;
266 out.open(path.value());
267 ASSERT_TRUE(out.is_open());
268 out << "test output";
269 EXPECT_TRUE(out.good());
270 out.close();
271
272 // Test reading
273 std::ifstream in;
274 in.open(path.value());
275 EXPECT_TRUE(in.is_open());
276 std::ostringstream sstr;
277 sstr << in.rdbuf();
278 EXPECT_EQ(sstr.str(), testContents);
279 in.close();
280
281 // Test deleting
282 EXPECT_TRUE(DeleteSystemFile(path.value().c_str()));
283 }
284
285 // Test retrieving page size
TEST(SystemUtils,PageSize)286 TEST(SystemUtils, PageSize)
287 {
288 size_t pageSize = GetPageSize();
289 EXPECT_TRUE(pageSize > 0);
290 }
291
292 // mprotect is not supported on Fuchsia right now.
293 #if defined(ANGLE_PLATFORM_FUCHSIA)
294 # define MAYBE_PageFaultHandlerInit DISABLED_PageFaultHandlerInit
295 # define MAYBE_PageFaultHandlerProtect DISABLED_PageFaultHandlerProtect
296 # define MAYBE_PageFaultHandlerDefaultHandler DISABLED_PageFaultHandlerDefaultHandler
297 // mprotect tests hang on macOS M1. Also applies to the iOS simulator.
298 #elif ANGLE_PLATFORM_MACOS || ANGLE_PLATFORM_IOS_FAMILY_SIMULATOR
299 # define MAYBE_PageFaultHandlerInit PageFaultHandlerInit
300 # define MAYBE_PageFaultHandlerProtect DISABLED_PageFaultHandlerProtect
301 # define MAYBE_PageFaultHandlerDefaultHandler DISABLED_PageFaultHandlerDefaultHandler
302 #else
303 # define MAYBE_PageFaultHandlerInit PageFaultHandlerInit
304 # define MAYBE_PageFaultHandlerProtect PageFaultHandlerProtect
305 # define MAYBE_PageFaultHandlerDefaultHandler PageFaultHandlerDefaultHandler
306 #endif // defined(ANGLE_PLATFORM_FUCHSIA)
307
308 // Test initializing and cleaning up page fault handler.
TEST(SystemUtils,MAYBE_PageFaultHandlerInit)309 TEST(SystemUtils, MAYBE_PageFaultHandlerInit)
310 {
311 PageFaultCallback callback = [](uintptr_t address) {
312 return PageFaultHandlerRangeType::InRange;
313 };
314
315 std::unique_ptr<PageFaultHandler> handler(CreatePageFaultHandler(callback));
316
317 EXPECT_TRUE(handler->enable());
318 EXPECT_TRUE(handler->disable());
319 }
320
321 // Test write protecting and unprotecting memory
TEST(SystemUtils,MAYBE_PageFaultHandlerProtect)322 TEST(SystemUtils, MAYBE_PageFaultHandlerProtect)
323 {
324 size_t pageSize = GetPageSize();
325 EXPECT_TRUE(pageSize > 0);
326
327 std::vector<float> data = std::vector<float>(100);
328 size_t dataSize = sizeof(float) * data.size();
329 uintptr_t dataStart = reinterpret_cast<uintptr_t>(data.data());
330 uintptr_t protectionStart = rx::roundDownPow2(dataStart, pageSize);
331 uintptr_t protectionEnd = rx::roundUpPow2(dataStart + dataSize, pageSize);
332
333 std::mutex mutex;
334 bool handlerCalled = false;
335
336 PageFaultCallback callback = [&mutex, pageSize, dataStart, dataSize,
337 &handlerCalled](uintptr_t address) {
338 if (address >= dataStart && address < dataStart + dataSize)
339 {
340 std::lock_guard<std::mutex> lock(mutex);
341 uintptr_t pageStart = rx::roundDownPow2(address, pageSize);
342 EXPECT_TRUE(UnprotectMemory(pageStart, pageSize));
343 handlerCalled = true;
344 return PageFaultHandlerRangeType::InRange;
345 }
346 else
347 {
348 return PageFaultHandlerRangeType::OutOfRange;
349 }
350 };
351
352 std::unique_ptr<PageFaultHandler> handler(CreatePageFaultHandler(callback));
353 handler->enable();
354
355 size_t protectionSize = protectionEnd - protectionStart;
356
357 // Test Protect
358 EXPECT_TRUE(ProtectMemory(protectionStart, protectionSize));
359
360 data[0] = 0.0;
361
362 {
363 std::lock_guard<std::mutex> lock(mutex);
364 EXPECT_TRUE(handlerCalled);
365 }
366
367 // Test Protect and unprotect
368 EXPECT_TRUE(ProtectMemory(protectionStart, protectionSize));
369 EXPECT_TRUE(UnprotectMemory(protectionStart, protectionSize));
370
371 handlerCalled = false;
372 data[0] = 0.0;
373 EXPECT_FALSE(handlerCalled);
374
375 // Clean up
376 EXPECT_TRUE(handler->disable());
377 }
378
379 // Tests basic usage of StripFilenameFromPath.
TEST(SystemUtils,StripFilenameFromPathUsage)380 TEST(SystemUtils, StripFilenameFromPathUsage)
381 {
382 EXPECT_EQ(StripFilenameFromPath("/path/to/tests/angle_tests"), "/path/to/tests");
383 EXPECT_EQ(StripFilenameFromPath("C:\\tests\\angle_tests.exe"), "C:\\tests");
384 EXPECT_EQ(StripFilenameFromPath("angle_tests"), "");
385 }
386
387 #if defined(ANGLE_PLATFORM_POSIX)
388 std::mutex gCustomHandlerMutex;
389 bool gCustomHandlerCalled = false;
390
CustomSegfaultHandlerFunction(int sig,siginfo_t * info,void * unused)391 void CustomSegfaultHandlerFunction(int sig, siginfo_t *info, void *unused)
392 {
393 std::lock_guard<std::mutex> lock(gCustomHandlerMutex);
394 gCustomHandlerCalled = true;
395 }
396
397 // Test if the default page fault handler is called on OutOfRange.
TEST(SystemUtils,MAYBE_PageFaultHandlerDefaultHandler)398 TEST(SystemUtils, MAYBE_PageFaultHandlerDefaultHandler)
399 {
400 size_t pageSize = GetPageSize();
401 EXPECT_TRUE(pageSize > 0);
402
403 std::vector<float> data = std::vector<float>(100);
404 size_t dataSize = sizeof(float) * data.size();
405 uintptr_t dataStart = reinterpret_cast<uintptr_t>(data.data());
406 uintptr_t protectionStart = rx::roundDownPow2(dataStart, pageSize);
407 uintptr_t protectionEnd = rx::roundUpPow2(dataStart + dataSize, pageSize);
408 size_t protectionSize = protectionEnd - protectionStart;
409
410 // Create custom handler
411 struct sigaction sigAction = {};
412 sigAction.sa_flags = SA_SIGINFO;
413 sigAction.sa_sigaction = &CustomSegfaultHandlerFunction;
414 sigemptyset(&sigAction.sa_mask);
415
416 struct sigaction oldSigSegv = {};
417 ASSERT_TRUE(sigaction(SIGSEGV, &sigAction, &oldSigSegv) == 0);
418 struct sigaction oldSigBus = {};
419 ASSERT_TRUE(sigaction(SIGBUS, &sigAction, &oldSigBus) == 0);
420
421 PageFaultCallback callback = [protectionStart, protectionSize](uintptr_t address) {
422 EXPECT_TRUE(UnprotectMemory(protectionStart, protectionSize));
423 return PageFaultHandlerRangeType::OutOfRange;
424 };
425
426 std::unique_ptr<PageFaultHandler> handler(CreatePageFaultHandler(callback));
427 handler->enable();
428
429 // Test Protect
430 EXPECT_TRUE(ProtectMemory(protectionStart, protectionSize));
431
432 data[0] = 0.0;
433
434 {
435 std::lock_guard<std::mutex> lock(gCustomHandlerMutex);
436 EXPECT_TRUE(gCustomHandlerCalled);
437 }
438
439 // Clean up
440 EXPECT_TRUE(handler->disable());
441
442 ASSERT_TRUE(sigaction(SIGSEGV, &oldSigSegv, nullptr) == 0);
443 ASSERT_TRUE(sigaction(SIGBUS, &oldSigBus, nullptr) == 0);
444 }
445 #else
TEST(SystemUtils,MAYBE_PageFaultHandlerDefaultHandler)446 TEST(SystemUtils, MAYBE_PageFaultHandlerDefaultHandler) {}
447 #endif
448
449 // Tests basic usage of GetCurrentThreadId.
TEST(SystemUtils,GetCurrentThreadId)450 TEST(SystemUtils, GetCurrentThreadId)
451 {
452 constexpr size_t kThreadCount = 64;
453
454 std::mutex mutex;
455 std::condition_variable condVar;
456
457 std::vector<std::thread> threads;
458 std::set<ThreadId> threadIds;
459 size_t readyCount = 0;
460
461 for (size_t i = 0; i < kThreadCount; ++i)
462 {
463 threads.emplace_back([&]() {
464 std::unique_lock<std::mutex> lock(mutex);
465
466 // Get threadId
467 const angle::ThreadId threadId = angle::GetCurrentThreadId();
468 EXPECT_NE(threadId, angle::InvalidThreadId());
469 threadIds.insert(threadId);
470
471 // Allow thread to finish only when all threads received the id.
472 // Otherwise new thread may reuse Id of already completed but not joined thread.
473 // Problem can be reproduced on Window platform, for example.
474 ++readyCount;
475 if (readyCount < kThreadCount)
476 {
477 condVar.wait(lock, [&]() { return readyCount == kThreadCount; });
478 }
479 else
480 {
481 condVar.notify_all();
482 }
483
484 // Check that threadId is still the same.
485 EXPECT_EQ(threadId, angle::GetCurrentThreadId());
486 });
487 }
488
489 for (size_t i = 0; i < kThreadCount; ++i)
490 {
491 threads[i].join();
492 }
493
494 // Check that all threadIds were unique.
495 EXPECT_EQ(threadIds.size(), kThreadCount);
496 }
497
498 } // anonymous namespace
499