SystemFileTest.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/IO/SystemFile.h>
  9. #include <AzCore/std/string/string.h>
  10. #include <AzCore/IO/Path/Path.h>
  11. #include <AzCore/UnitTest/TestTypes.h>
  12. #include <AZTestShared/Utils/Utils.h>
  13. namespace UnitTest
  14. {
  15. static constexpr int s_numTrialsToPerform = 20000; // as much we can do in about a second. Increase for a deeper longer fuzz test
  16. /**
  17. * systemFile test
  18. */
  19. class SystemFileTest
  20. : public LeakDetectionFixture
  21. {
  22. protected:
  23. AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
  24. };
  25. #if AZ_TRAIT_DISABLE_FAILED_SYSTEM_FILE_TESTS
  26. TEST_F(SystemFileTest, DISABLED_Test)
  27. #else
  28. TEST_F(SystemFileTest, Can_CheckAttributesAndReadWriteFiles_Succeed)
  29. #endif // AZ_TRAIT_DISABLE_FAILED_SYSTEM_FILE_TESTS
  30. {
  31. // Test Deleting a non-existent file
  32. AZ::IO::Path invalidFileName = m_tempDirectory.Resolve("InvalidFileName");
  33. EXPECT_FALSE(AZ::IO::SystemFile::Exists(invalidFileName.c_str()));
  34. EXPECT_FALSE(AZ::IO::SystemFile::Delete(invalidFileName.c_str()));
  35. //Files that don't exist are not considered writable
  36. EXPECT_FALSE(AZ::IO::SystemFile::IsWritable(invalidFileName.c_str()));
  37. AZ::IO::Path testFile = m_tempDirectory.Resolve("SystemFileTest.txt");
  38. AZStd::string testString = "Hello";
  39. // If file exists start by deleting the file
  40. AZ::IO::SystemFile::Delete(testFile.c_str());
  41. EXPECT_FALSE(AZ::IO::SystemFile::Exists(testFile.c_str()));
  42. // Test Writing a file
  43. {
  44. AZ::IO::SystemFile oFile;
  45. oFile.Open(testFile.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
  46. oFile.Write(testString.c_str(), testString.length() + 1);
  47. EXPECT_EQ(testString.length() + 1, oFile.Tell());
  48. EXPECT_TRUE(oFile.Eof());
  49. oFile.Flush();
  50. oFile.Close();
  51. EXPECT_TRUE(AZ::IO::SystemFile::Exists(testFile.c_str()));
  52. }
  53. // Test Reading from the file
  54. {
  55. AZ::IO::SystemFile iFile;
  56. iFile.Open(testFile.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY);
  57. EXPECT_TRUE(iFile.IsOpen());
  58. char* buffer = (char*)azmalloc(testString.length() + 1);
  59. AZ::IO::SystemFile::SizeType nRead = iFile.Read(testString.length() + 1, buffer);
  60. EXPECT_EQ(testString.length() + 1, nRead);
  61. EXPECT_EQ(0, strcmp(testString.c_str(), buffer));
  62. EXPECT_EQ(testString.length() + 1, iFile.Tell());
  63. EXPECT_TRUE(iFile.Eof());
  64. iFile.Close();
  65. EXPECT_FALSE(iFile.IsOpen());
  66. azfree(buffer);
  67. }
  68. //Test Appending to the file
  69. {
  70. AZ::IO::SystemFile oFile;
  71. oFile.Open(testFile.c_str(), AZ::IO::SystemFile::SF_OPEN_APPEND | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
  72. AZ::IO::SystemFile::SizeType initialSize = oFile.Length();
  73. oFile.Write(testString.c_str(), testString.length() + 1);
  74. oFile.Write(testString.c_str(), testString.length() + 1);
  75. EXPECT_EQ(initialSize + (testString.length() + 1) * 2, oFile.Tell());
  76. EXPECT_TRUE(oFile.Eof());
  77. oFile.Flush();
  78. oFile.Close();
  79. EXPECT_TRUE(AZ::IO::SystemFile::Exists(testFile.c_str()));
  80. }
  81. // Test Reading from the file
  82. {
  83. AZ::IO::SystemFile iFile;
  84. iFile.Open(testFile.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY);
  85. EXPECT_TRUE(iFile.IsOpen());
  86. size_t len = testString.length() + 1;
  87. char* buffer = (char*)azmalloc(len * 3);
  88. AZ::IO::SystemFile::SizeType nRead = iFile.Read(len * 3, buffer);
  89. EXPECT_EQ(len * 3, nRead);
  90. EXPECT_EQ(0, strcmp(testString.c_str(), buffer));
  91. EXPECT_EQ(0, strcmp(testString.c_str(), buffer + len));
  92. EXPECT_EQ(0, strcmp(testString.c_str(), buffer + (len * 2)));
  93. EXPECT_EQ(nRead, iFile.Tell());
  94. EXPECT_TRUE(iFile.Eof());
  95. iFile.Close();
  96. EXPECT_FALSE(iFile.IsOpen());
  97. azfree(buffer);
  98. }
  99. //File should exist now, and be writable
  100. EXPECT_TRUE(AZ::IO::SystemFile::IsWritable(testFile.c_str()));
  101. #if AZ_TRAIT_OS_CAN_SET_FILES_WRITABLE
  102. AZ::IO::SystemFile::SetWritable(testFile.c_str(), false);
  103. EXPECT_FALSE(AZ::IO::SystemFile::IsWritable(testFile.c_str()));
  104. AZ::IO::SystemFile::SetWritable(testFile.c_str(), true);
  105. EXPECT_TRUE(AZ::IO::SystemFile::IsWritable(testFile.c_str()));
  106. #endif
  107. // Now that the file exists, verify that we can delete it
  108. bool deleted = AZ::IO::SystemFile::Delete(testFile.c_str());
  109. EXPECT_TRUE(deleted);
  110. EXPECT_FALSE(AZ::IO::SystemFile::Exists(testFile.c_str()));
  111. }
  112. TEST_F(SystemFileTest, Open_NullFileNames_DoesNotCrash)
  113. {
  114. AZ::IO::SystemFile oFile;
  115. EXPECT_FALSE(oFile.Open(nullptr, AZ::IO::SystemFile::SF_OPEN_READ_ONLY));
  116. }
  117. TEST_F(SystemFileTest, Open_EmptyFileNames_DoesNotCrash)
  118. {
  119. AZ::IO::SystemFile oFile;
  120. EXPECT_FALSE(oFile.Open("", AZ::IO::SystemFile::SF_OPEN_READ_ONLY));
  121. }
  122. TEST_F(SystemFileTest, Open_BadFileNames_DoesNotCrash)
  123. {
  124. AZStd::string randomJunkName;
  125. randomJunkName.resize(128, '\0');
  126. for (int trialNumber = 0; trialNumber < s_numTrialsToPerform; ++trialNumber)
  127. {
  128. for (int randomChar = 0; randomChar < randomJunkName.size(); ++randomChar)
  129. {
  130. // note that this is intentionally allowing null characters to generate.
  131. // note that this also puts characters AFTER the null, if a null appears in the mddle.
  132. // so that if there are off by one errors they could include cruft afterwards.
  133. if (randomChar > trialNumber % randomJunkName.size())
  134. {
  135. // choose this point for the nulls to begin. It makes sure we test every size of string.
  136. randomJunkName[randomChar] = 0;
  137. }
  138. else
  139. {
  140. randomJunkName[randomChar] = (char)(rand() % 256); // this will trigger invalid UTF8 decoding too
  141. }
  142. }
  143. AZ::IO::SystemFile oFile;
  144. oFile.Open(randomJunkName.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY);
  145. }
  146. }
  147. #if AZ_TRAIT_DISABLE_FAILED_FILE_DESCRIPTOR_REDIRECTOR_TESTS
  148. TEST_F(SystemFileTest, DISABLED_FileDescriptorRedirector_MainFunctionality)
  149. #else
  150. TEST_F(SystemFileTest, FileDescriptorRedirector_MainFunctionality)
  151. #endif // AZ_TRAIT_DISABLE_FAILED_FILE_DESCRIPTOR_REDIRECTOR_TESTS
  152. {
  153. AZ::Test::ScopedAutoTempDirectory tempDir;
  154. auto srcFile = tempDir.Resolve("SystemFileTest_Source.txt");
  155. auto redirectFile = tempDir.Resolve("SystemFileTest_Redirected.txt");
  156. {
  157. AZ::IO::SystemFile oFile;
  158. oFile.Open(srcFile.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE);
  159. oFile.Close();
  160. oFile.Open(redirectFile.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE);
  161. oFile.Close();
  162. }
  163. auto stringInFile = [](const char* filename, const char* findStr)
  164. {
  165. char content[128];
  166. size_t bytes = AZ::IO::SystemFile::Read(filename, content, sizeof(content));
  167. return bytes > 0 ? strstr(content, findStr) != nullptr : false;
  168. };
  169. const char contentInSource[] = "SOURCE";
  170. const char contentInRedirect[] = "REDIRECT";
  171. using namespace AZ::IO;
  172. // Before redirection, source must be written like normal, nothing should be in the redirection
  173. {
  174. int sourceFd = PosixInternal::Open(srcFile.c_str(),
  175. PosixInternal::OpenFlags::Create | PosixInternal::OpenFlags::WriteOnly | PosixInternal::OpenFlags::Truncate,
  176. PosixInternal::PermissionModeFlags::Read | PosixInternal::PermissionModeFlags::Write);
  177. FileDescriptorRedirector redirector(sourceFd);
  178. PosixInternal::Write(sourceFd, contentInSource, sizeof(contentInSource));
  179. PosixInternal::Close(sourceFd);
  180. }
  181. EXPECT_TRUE(stringInFile(srcFile.c_str(), contentInSource));
  182. EXPECT_FALSE(stringInFile(redirectFile.c_str(), contentInSource));
  183. // After redirection, redirection must be written
  184. {
  185. int sourceFd = PosixInternal::Open(srcFile.c_str(),
  186. PosixInternal::OpenFlags::Create | PosixInternal::OpenFlags::WriteOnly | PosixInternal::OpenFlags::Truncate,
  187. PosixInternal::PermissionModeFlags::Read | PosixInternal::PermissionModeFlags::Write);
  188. FileDescriptorRedirector redirector(sourceFd);
  189. redirector.RedirectTo(redirectFile.Native(), FileDescriptorRedirector::Mode::Create);
  190. PosixInternal::Write(sourceFd, contentInRedirect, sizeof(contentInRedirect));
  191. PosixInternal::Close(sourceFd);
  192. }
  193. EXPECT_FALSE(stringInFile(srcFile.c_str(), contentInRedirect));
  194. EXPECT_TRUE(stringInFile(redirectFile.c_str(), contentInRedirect));
  195. // After removing redirection, source must be written
  196. {
  197. int sourceFd = AZ::IO::PosixInternal::Open(srcFile.c_str(),
  198. PosixInternal::OpenFlags::Create | PosixInternal::OpenFlags::WriteOnly | PosixInternal::OpenFlags::Truncate,
  199. PosixInternal::PermissionModeFlags::Read | PosixInternal::PermissionModeFlags::Write);
  200. FileDescriptorRedirector redirector(sourceFd);
  201. redirector.RedirectTo(redirectFile.Native(), FileDescriptorRedirector::Mode::Create);
  202. redirector.Reset();
  203. PosixInternal::Write(sourceFd, contentInSource, sizeof(contentInSource));
  204. PosixInternal::Close(sourceFd);
  205. }
  206. EXPECT_TRUE(stringInFile(srcFile.c_str(), contentInSource));
  207. EXPECT_FALSE(stringInFile(redirectFile.c_str(), contentInSource));
  208. // Check that bypassing output even after being redirected, writes to the original file
  209. {
  210. int sourceFd = AZ::IO::PosixInternal::Open(srcFile.c_str(),
  211. PosixInternal::OpenFlags::Create | PosixInternal::OpenFlags::WriteOnly | PosixInternal::OpenFlags::Truncate,
  212. PosixInternal::PermissionModeFlags::Read | PosixInternal::PermissionModeFlags::Write);
  213. FileDescriptorRedirector redirector(sourceFd);
  214. redirector.RedirectTo(redirectFile.Native(), FileDescriptorRedirector::Mode::Create);
  215. redirector.WriteBypassingRedirect(contentInSource, sizeof(contentInSource));
  216. PosixInternal::Close(sourceFd);
  217. }
  218. EXPECT_TRUE(stringInFile(srcFile.c_str(), contentInSource));
  219. EXPECT_FALSE(stringInFile(redirectFile.c_str(), contentInSource));
  220. AZ::IO::SystemFile::Delete(srcFile.c_str());
  221. AZ::IO::SystemFile::Delete(redirectFile.c_str());
  222. }
  223. TEST_F(SystemFileTest, FileDescriptorCapturer_DoesNotDeadlock_WhenMoreThanPipeSizeContent_IsCaptured)
  224. {
  225. using namespace AZ::IO;
  226. AZStd::string capturedData;
  227. auto StoreFromFile = [&capturedData](AZStd::span<const AZStd::byte> capturedBytes)
  228. {
  229. AZStd::string_view capturedStrView(reinterpret_cast<const char*>(capturedBytes.data()), capturedBytes.size());
  230. capturedData += capturedStrView;
  231. };
  232. // The +1 to make sure the value isn't a power of 2, to try to catch any edge cases
  233. // with reading from a buffered pipe
  234. constexpr size_t charCountToWrite = AZ::IO::FileDescriptorCapturer::DefaultPipeSize * 2 + 1;
  235. capturedData.reserve(charCountToWrite);
  236. // while it may be tempting to use stdout here, other processes and threads might be running
  237. // in the same test run and also outputting to stdout. This would cause an intermittent failure.
  238. // Instead, write to a temp file.
  239. AZ::Test::ScopedAutoTempDirectory tempDir;
  240. auto srcFile = tempDir.Resolve("SystemFileTest_Source.txt");
  241. int sourceFd = PosixInternal::Open(srcFile.c_str(),
  242. PosixInternal::OpenFlags::Create | PosixInternal::OpenFlags::ReadWrite | PosixInternal::OpenFlags::Truncate,
  243. PosixInternal::PermissionModeFlags::Read | PosixInternal::PermissionModeFlags::Write);
  244. ASSERT_NE(sourceFd, -1);
  245. AZ::IO::FileDescriptorCapturer capturer(sourceFd);
  246. capturer.Start(StoreFromFile);
  247. const char* dataToWrite = "a";
  248. for (size_t i = 0; i < charCountToWrite; ++i)
  249. {
  250. // this should cause the write function to fill up any buffer and then block if the pipe is full
  251. // until the capturer reads from it.
  252. // filling the pipe should NOT cause blocking here, since the capturer reads on a different thread.
  253. PosixInternal::Write(sourceFd, dataToWrite, 1);
  254. }
  255. PosixInternal::Close(sourceFd);
  256. capturer.Stop();
  257. const AZStd::string expectedValue(charCountToWrite, 'a');
  258. // if the lengths are not equal, we DROPPED or invented data.
  259. EXPECT_EQ(expectedValue.size(), capturedData.size()) << "FileDescriptorCapturer dropped or invented some of the data.";
  260. if (expectedValue.size() == capturedData.size())
  261. {
  262. // if the lengths are equal, but the strings are different, then the data is corrupt in some way (full of the
  263. // wrong character, or nulls, or something).
  264. EXPECT_TRUE(expectedValue == capturedData) << "FileDescriptorCapturer corrupted some of the data";
  265. }
  266. }
  267. TEST_F(SystemFileTest, GetStdout_ReturnsHandle_ThatCanWriteToStdout_Succeeds)
  268. {
  269. AZStd::string stdoutData;
  270. auto StoreStdout = [&stdoutData](AZStd::span<const AZStd::byte> capturedBytes)
  271. {
  272. AZStd::string_view capturedStrView(reinterpret_cast<const char*>(capturedBytes.data()), capturedBytes.size());
  273. stdoutData += capturedStrView;
  274. };
  275. constexpr AZStd::string_view testData{ "Micro Macro Tera Zetta\n"};
  276. // Capture stdout using descriptor of 1
  277. constexpr int StdoutDescriptor = 1;
  278. AZ::IO::FileDescriptorCapturer capturer(StdoutDescriptor);
  279. capturer.Start(StoreStdout);
  280. {
  281. AZ::IO::SystemFile stdoutHandle = AZ::IO::SystemFile::GetStdout();
  282. stdoutHandle.Write(testData.data(), testData.size());
  283. }
  284. fflush(stdout);
  285. capturer.Stop();
  286. EXPECT_EQ(testData, stdoutData);
  287. }
  288. TEST_F(SystemFileTest, GetStderr_ReturnsHandle_ThatCanWriteToStderr_Succeeds)
  289. {
  290. AZStd::string stderrData;
  291. auto StoreStderr = [&stderrData](AZStd::span<const AZStd::byte> capturedBytes)
  292. {
  293. AZStd::string_view capturedStrView(reinterpret_cast<const char*>(capturedBytes.data()), capturedBytes.size());
  294. stderrData += capturedStrView;
  295. };
  296. constexpr AZStd::string_view testData{ "Micro Macro Tera Zetta\n"};
  297. // Capture stderr using descriptor of 2
  298. constexpr int StderrDescriptor = 2;
  299. AZ::IO::FileDescriptorCapturer capturer(StderrDescriptor);
  300. capturer.Start(StoreStderr);
  301. {
  302. AZ::IO::SystemFile stderrHandle = AZ::IO::SystemFile::GetStderr();
  303. stderrHandle.Write(testData.data(), testData.size());
  304. }
  305. fflush(stderr);
  306. capturer.Stop();
  307. EXPECT_EQ(testData, stderrData);
  308. }
  309. TEST_F(SystemFileTest, GetStdin_ReturnsHandle_ThatIsOpen_Succeeds)
  310. {
  311. char buffer[1];
  312. AZ::IO::SystemFile stdinHandle = AZ::IO::SystemFile::GetStdin();
  313. ASSERT_TRUE(stdinHandle.IsOpen());
  314. // Read 0 bytes to validate that the stdin handle is open
  315. // as well to avoid needing stdin to have data within it
  316. // This call should block.
  317. EXPECT_EQ(0, stdinHandle.Read(static_cast<AZ::IO::SizeType>(0), buffer));
  318. }
  319. } // namespace UnitTest