SystemFileTest.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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. } // namespace UnitTest