#include #ifdef _unix_ #include #endif #include "filemap.h" #include #include #include Y_UNIT_TEST_SUITE(TFileMapTest) { static const char* FileName_("./mappped_file"); void BasicTest(TMemoryMapCommon::EOpenMode mode) { char data[] = "abcdefgh"; TFile file(FileName_, CreateAlways | WrOnly); file.Write(static_cast(data), sizeof(data)); file.Close(); { TFileMap mappedFile(FileName_, mode); mappedFile.Map(0, mappedFile.Length()); UNIT_ASSERT(mappedFile.MappedSize() == sizeof(data) && mappedFile.Length() == sizeof(data)); UNIT_ASSERT(mappedFile.IsOpen()); for (size_t i = 0; i < sizeof(data); ++i) { UNIT_ASSERT(static_cast(mappedFile.Ptr())[i] == data[i]); static_cast(mappedFile.Ptr())[i] = data[i] + 1; } mappedFile.Flush(); TFileMap::TMapResult mapResult = mappedFile.Map(2, 2); UNIT_ASSERT(mapResult.MappedSize() == 2); UNIT_ASSERT(mapResult.MappedData() == mappedFile.Ptr()); UNIT_ASSERT(mappedFile.MappedSize() == 2); UNIT_ASSERT(static_cast(mappedFile.Ptr())[0] == 'd' && static_cast(mappedFile.Ptr())[1] == 'e'); mappedFile.Unmap(); UNIT_ASSERT(mappedFile.MappedSize() == 0); FILE* f = fopen(FileName_, "rb"); TFileMap mappedFile2(f); mappedFile2.Map(0, mappedFile2.Length()); UNIT_ASSERT(mappedFile2.MappedSize() == sizeof(data)); UNIT_ASSERT(static_cast(mappedFile2.Ptr())[0] == data[0] + 1); fclose(f); } NFs::Remove(FileName_); } Y_UNIT_TEST(TestFileMap) { BasicTest(TMemoryMapCommon::oRdWr); } Y_UNIT_TEST(TestFileMapPopulate) { BasicTest(TMemoryMapCommon::oRdWr | TMemoryMapCommon::oPopulate); } Y_UNIT_TEST(TestFileRemap) { const char data1[] = "01234"; const char data2[] = "abcdefg"; const char data3[] = "COPY"; const char dataFinal[] = "012abcdefg"; const size_t data2Shift = 3; TFile file(FileName_, CreateAlways | WrOnly); file.Write(static_cast(data1), sizeof(data1)); file.Close(); { TFileMap mappedFile(FileName_, TMemoryMapCommon::oRdWr); mappedFile.Map(0, mappedFile.Length()); UNIT_ASSERT(mappedFile.MappedSize() == sizeof(data1) && mappedFile.Length() == sizeof(data1)); mappedFile.ResizeAndRemap(data2Shift, sizeof(data2)); memcpy(mappedFile.Ptr(), data2, sizeof(data2)); } { TFileMap mappedFile(FileName_, TMemoryMapCommon::oCopyOnWr); mappedFile.Map(0, mappedFile.Length()); UNIT_ASSERT(mappedFile.MappedSize() == sizeof(dataFinal) && mappedFile.Length() == sizeof(dataFinal)); char* data = static_cast(mappedFile.Ptr()); UNIT_ASSERT(data[0] == '0'); UNIT_ASSERT(data[3] == 'a'); memcpy(data, data3, sizeof(data3)); UNIT_ASSERT(data[0] == 'C'); UNIT_ASSERT(data[3] == 'Y'); } TFile resFile(FileName_, RdOnly); UNIT_ASSERT(resFile.GetLength() == sizeof(dataFinal)); char buf[sizeof(dataFinal)]; resFile.Read(buf, sizeof(dataFinal)); UNIT_ASSERT(0 == memcmp(buf, dataFinal, sizeof(dataFinal))); resFile.Close(); NFs::Remove(FileName_); } Y_UNIT_TEST(TestFileMapDbgName) { // This test checks that dbgName passed to the TFileMap constructor is saved inside the object and appears // in subsequent error messages. const char* const dbgName = "THIS_IS_A_TEST"; FILE* f = fopen(FileName_, "w+"); UNIT_ASSERT(f); { TFileMap mappedFile(f, TFileMap::oRdWr, dbgName); bool gotException = false; try { // trying to map an empty file to force an exception and check the message mappedFile.Map(0, 1000); } catch (const yexception& e) { gotException = true; UNIT_ASSERT_STRING_CONTAINS(e.what(), dbgName); } UNIT_ASSERT(gotException); } fclose(f); NFs::Remove(FileName_); } #if defined(_asan_enabled_) || defined(_msan_enabled_) // setrlimit incompatible with asan runtime #elif defined(_cygwin_) // cygwin is not real unix :( #else Y_UNIT_TEST(TestNotGreedy) { unsigned page[4096 / sizeof(unsigned)]; #if defined(_unix_) // Temporary limit allowed virtual memory size to 1Gb struct rlimit rlim; if (getrlimit(RLIMIT_AS, &rlim)) { throw TSystemError() << "Cannot get rlimit for virtual memory"; } rlim_t Limit = 1 * 1024 * 1024 * 1024; if (rlim.rlim_cur > Limit) { rlim.rlim_cur = Limit; if (setrlimit(RLIMIT_AS, &rlim)) { throw TSystemError() << "Cannot set rlimit for virtual memory to 1Gb"; } } #endif // Make a 128M test file try { TFile file(FileName_, CreateAlways | WrOnly); for (unsigned pages = 128 * 1024 * 1024 / sizeof(page), i = 0; pages--; i++) { std::fill(page, page + sizeof(page) / sizeof(*page), i); file.Write(page, sizeof(page)); } file.Close(); // Make 16 maps of our file, which would require 16*128M = 2Gb and exceed our 1Gb limit TVector> maps; for (int i = 0; i < 16; ++i) { maps.emplace_back(MakeHolder(FileName_, TMemoryMapCommon::oRdOnly | TMemoryMapCommon::oNotGreedy)); maps.back()->Map(i * sizeof(page), sizeof(page)); } // Oh, good, we're not dead yet for (int i = 0; i < 16; ++i) { TFileMap& map = *maps[i]; UNIT_ASSERT_EQUAL(map.Length(), 128 * 1024 * 1024); UNIT_ASSERT_EQUAL(map.MappedSize(), sizeof(page)); const int* mappedPage = (const int*)map.Ptr(); for (size_t j = 0; j < sizeof(page) / sizeof(*page); ++j) { UNIT_ASSERT_EQUAL(mappedPage[j], i); } } #if defined(_unix_) // Restore limits and cleanup rlim.rlim_cur = rlim.rlim_max; if (setrlimit(RLIMIT_AS, &rlim)) { throw TSystemError() << "Cannot restore rlimit for virtual memory"; } #endif maps.clear(); NFs::Remove(FileName_); } catch (...) { // TODO: RAII'ize all this stuff #if defined(_unix_) rlim.rlim_cur = rlim.rlim_max; if (setrlimit(RLIMIT_AS, &rlim)) { throw TSystemError() << "Cannot restore rlimit for virtual memory"; } #endif NFs::Remove(FileName_); throw; } } #endif Y_UNIT_TEST(TestFileMappedArray) { { TFileMappedArray mappedArray; ui32 data[] = {123, 456, 789, 10}; size_t sz = sizeof(data) / sizeof(data[0]); TFile file(FileName_, CreateAlways | WrOnly); file.Write(static_cast(data), sizeof(data)); file.Close(); mappedArray.Init(FileName_); // actual test begin UNIT_ASSERT(mappedArray.Size() == sz); for (size_t i = 0; i < sz; ++i) { UNIT_ASSERT(mappedArray[i] == data[i]); } UNIT_ASSERT(mappedArray.GetAt(mappedArray.Size()) == 0); UNIT_ASSERT(*mappedArray.Begin() == data[0]); UNIT_ASSERT(size_t(mappedArray.End() - mappedArray.Begin()) == sz); UNIT_ASSERT(!mappedArray.Empty()); // actual test end mappedArray.Term(); // Init array via file mapping TFileMap fileMap(FileName_); fileMap.Map(0, fileMap.Length()); mappedArray.Init(fileMap); // actual test begin UNIT_ASSERT(mappedArray.Size() == sz); for (size_t i = 0; i < sz; ++i) { UNIT_ASSERT(mappedArray[i] == data[i]); } UNIT_ASSERT(mappedArray.GetAt(mappedArray.Size()) == 0); UNIT_ASSERT(*mappedArray.Begin() == data[0]); UNIT_ASSERT(size_t(mappedArray.End() - mappedArray.Begin()) == sz); UNIT_ASSERT(!mappedArray.Empty()); // actual test end file = TFile(FileName_, WrOnly); file.Seek(0, sEnd); file.Write("x", 1); file.Close(); bool caught = false; try { mappedArray.Init(FileName_); } catch (const yexception&) { caught = true; } UNIT_ASSERT(caught); } NFs::Remove(FileName_); } Y_UNIT_TEST(TestMappedArray) { ui32 sz = 10; TMappedArray mappedArray; ui32* ptr = mappedArray.Create(sz); UNIT_ASSERT(ptr != nullptr); UNIT_ASSERT(mappedArray.size() == sz); UNIT_ASSERT(mappedArray.begin() + sz == mappedArray.end()); for (size_t i = 0; i < sz; ++i) { mappedArray[i] = (ui32)i; } for (size_t i = 0; i < sz; ++i) { UNIT_ASSERT(mappedArray[i] == i); } TMappedArray mappedArray2(1000); mappedArray.swap(mappedArray2); UNIT_ASSERT(mappedArray.size() == 1000 && mappedArray2.size() == sz); } Y_UNIT_TEST(TestMemoryMap) { TFile file(FileName_, CreateAlways | WrOnly); file.Close(); FILE* f = fopen(FileName_, "rb"); UNIT_ASSERT(f != nullptr); try { TMemoryMap mappedMem(f); mappedMem.Map(mappedMem.Length() / 2, mappedMem.Length() + 100); // overflow UNIT_ASSERT(0); // should not go here } catch (yexception& exc) { TString text = exc.what(); // exception should contain failed file name UNIT_ASSERT(text.find(TMemoryMapCommon::UnknownFileName()) != TString::npos); fclose(f); } TFile fileForMap(FileName_, OpenExisting); try { TMemoryMap mappedMem(fileForMap); mappedMem.Map(mappedMem.Length() / 2, mappedMem.Length() + 100); // overflow UNIT_ASSERT(0); // should not go here } catch (yexception& exc) { TString text = exc.what(); // exception should contain failed file name UNIT_ASSERT(text.find(FileName_) != TString::npos); } NFs::Remove(FileName_); } Y_UNIT_TEST(TestMemoryMapIsWritable) { TFile file(FileName_, CreateAlways | WrOnly); file.Close(); { TMemoryMap mappedMem(FileName_, TMemoryMap::oRdOnly); UNIT_ASSERT(!mappedMem.IsWritable()); } { TMemoryMap mappedMem(FileName_, TMemoryMap::oRdWr); UNIT_ASSERT(mappedMem.IsWritable()); } NFs::Remove(FileName_); } Y_UNIT_TEST(TestFileMapIsWritable) { TFile file(FileName_, CreateAlways | WrOnly); file.Close(); { TMemoryMap mappedMem(FileName_, TMemoryMap::oRdOnly); TFileMap fileMap(mappedMem); UNIT_ASSERT(!fileMap.IsWritable()); } { TMemoryMap mappedMem(FileName_, TMemoryMap::oRdWr); TFileMap fileMap(mappedMem); UNIT_ASSERT(fileMap.IsWritable()); } { TFileMap fileMap(FileName_, TFileMap::oRdOnly); UNIT_ASSERT(!fileMap.IsWritable()); } { TFileMap fileMap(FileName_, TFileMap::oRdWr); UNIT_ASSERT(fileMap.IsWritable()); } NFs::Remove(FileName_); } } // Y_UNIT_TEST_SUITE(TFileMapTest)