@@ -66,7 +66,7 @@ TEST_F(MappedFileFixture, FileHandle) {
6666 EXPECT_TRUE (file); // a bit pointless - would have thrown if not
6767}
6868
69- TEST_F (MappedFileFixture , Create) {
69+ TEST (MappedFile , Create) {
7070 fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
7171 EXPECT_FALSE (fs::exists (tmpFile2));
7272 if (fs::exists (tmpFile2))
@@ -93,7 +93,7 @@ TEST_F(MappedFileFixture, Create) {
9393 EXPECT_FALSE (fs::exists (tmpFile2));
9494}
9595
96- TEST_F (MappedFileFixture , Reserve) {
96+ TEST (MappedFile , Reserve) {
9797 fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
9898 {
9999 // Create a new file
@@ -154,7 +154,28 @@ TEST_F(MappedFileFixture, LinuxFileDescriptor) {
154154 EXPECT_NE (fd, -1 );
155155}
156156
157- TEST_F (MappedFileFixture, LinuxCreate) {
157+ TEST_F (MappedFileFixture, LinuxOvermapResizeWrite) {
158+ size_t overmapSize = 1024 * 1024 ;
159+ EXPECT_EQ (fs::file_size (m_tmpFile), sizeof (int ));
160+ {
161+ detail::FileDescriptor fd (m_tmpFile, O_RDWR);
162+ detail::MemoryMapRW mapped (nullptr , overmapSize, MAP_SHARED, fd, 0 );
163+ fd.truncate (overmapSize);
164+
165+ std::span data (reinterpret_cast <uint8_t *>(mapped.address ()), mapped.size ());
166+ data.back () = 142 ;
167+ }
168+ EXPECT_EQ (fs::file_size (m_tmpFile), overmapSize);
169+ {
170+ std::ifstream ifile (m_tmpFile, std::ios::binary);
171+ uint8_t lastByte;
172+ ifile.seekg (overmapSize - sizeof (lastByte));
173+ ifile.read (reinterpret_cast <char *>(&lastByte), sizeof (lastByte));
174+ EXPECT_EQ (lastByte, 142 );
175+ }
176+ }
177+
178+ TEST (MappedFile, LinuxCreate) {
158179 fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
159180 EXPECT_FALSE (fs::exists (tmpFile2));
160181 {
@@ -186,7 +207,7 @@ TEST_F(MappedFileFixture, LinuxReserve) {
186207 EXPECT_EQ (*reinterpret_cast <const int *>(mapped.address ()), 42 );
187208}
188209
189- TEST_F (MappedFileFixture , LinuxResize) {
210+ TEST (MappedFile , LinuxResize) {
190211 // Reserve some virtual address space
191212 detail::MemoryMap<PROT_NONE> reserved (nullptr , detail::pageSize () * 4 ,
192213 MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );
@@ -250,9 +271,86 @@ TEST_F(MappedFileFixture, LinuxResize) {
250271 EXPECT_FALSE (fs::exists (tmpFile2));
251272}
252273
253- // TODO:
254- // - MAP_HUGETLB
255- // - MAP_HUGE_2MB, MAP_HUGE_1GB
274+ std::vector<unsigned char > getResidency (void * base, size_t size) {
275+ std::vector<unsigned char > result (size / getpagesize (), 0u );
276+ int ret = mincore (base, size, result.data ());
277+ if (ret != 0 )
278+ throw detail::LastError ();
279+ return result;
280+ }
281+
282+ TEST_F (MappedFileFixture, LinuxResidencyAfterTruncate) {
283+ // Map the 1-int sized file to a much larger range
284+ EXPECT_EQ (fs::file_size (m_tmpFile), sizeof (int ));
285+ size_t newSize = 16 * 1024 * 1024 ;
286+ detail::FileDescriptor fd (m_tmpFile, O_RDWR);
287+ detail::MemoryMap<PROT_READ | PROT_WRITE> mapped (nullptr , newSize, MAP_SHARED, fd, 0 );
288+
289+ // Increase the file size to match the mapping and check no pages are
290+ // resident yet. Actually the first page is, but at least the rest should be
291+ // untouched.
292+ fd.truncate (newSize);
293+ EXPECT_TRUE (
294+ std::ranges::all_of (getResidency (mapped.address (getpagesize ()), newSize - getpagesize ()),
295+ [](unsigned char c) { return (c & 1u ) == 0 ; }));
296+
297+ // Fill the conents of the file and confirm pages are allocated
298+ std::ranges::fill (std::span (reinterpret_cast <uint8_t *>(mapped.address ()), newSize),
299+ uint8_t (0xff ));
300+ EXPECT_TRUE (std::ranges::all_of (getResidency (mapped.address (), newSize),
301+ [](unsigned char c) { return (c & 1u ) == 1 ; }));
302+
303+ // Empty the file and check those pages are no longer resident
304+ fd.truncate (0 );
305+ EXPECT_TRUE (std::ranges::all_of (getResidency (mapped.address (), newSize),
306+ [](unsigned char c) { return (c & 1u ) == 0 ; }));
307+ EXPECT_EQ (fs::file_size (m_tmpFile), 0 );
308+ }
309+
310+ TEST (MappedMemory, LinuxResidencyAfterDecommit) {
311+ const size_t page_size = getpagesize ();
312+ const size_t reserve_size = page_size * 64 ; // 64 pages total
313+ const size_t commit_size = page_size * 4 ; // We'll use 4 pages
314+
315+ // Reserve virtual address space (uncommitted, inaccessible)
316+ void * base =
317+ mmap (nullptr , reserve_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1 , 0 );
318+ ASSERT_NE (base, MAP_FAILED) << " Failed to mmap reserved space" ;
319+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
320+ [](unsigned char c) { return (c & 1u ) == 0 ; }));
321+
322+ // Commit a portion with PROT_READ | PROT_WRITE
323+ int prot_result = mprotect (base, commit_size, PROT_READ | PROT_WRITE);
324+ ASSERT_EQ (prot_result, 0 ) << " Failed to mprotect committed region" ;
325+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
326+ [](unsigned char c) { return (c & 1u ) == 0 ; }));
327+
328+ // Touch the memory to ensure it's backed by RAM
329+ std::span committed (static_cast <std::byte*>(base), commit_size);
330+ std::ranges::fill (committed, std::byte (0xAB ));
331+
332+ // Verify pages are resident using mincore
333+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
334+ [](unsigned char c) { return (c & 1u ) == 1 ; }));
335+
336+ // Decommit
337+ #if 0
338+ void* remap = mmap(base, commit_size, PROT_NONE,
339+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED, -1, 0);
340+ ASSERT_EQ(remap, base) << "Failed to remap to decommit pages";
341+ #else
342+ // See MADV_FREE discussion here: https://github.com/golang/go/issues/42330
343+ prot_result = mprotect (base, commit_size, PROT_NONE);
344+ ASSERT_EQ (prot_result, 0 ) << " Failed to mprotect committed region back to PROT_NONE" ;
345+ int madvise_result = madvise (base, commit_size, MADV_DONTNEED);
346+ ASSERT_EQ (madvise_result, 0 ) << " Failed to release pages with madvise" ;
347+ #endif
348+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
349+ [](uint8_t c) { return (c & 1u ) == 0 ; }));
350+
351+ // Cleanup
352+ munmap (base, reserve_size);
353+ }
256354
257355#endif
258356
@@ -456,8 +554,34 @@ TEST_F(MappedFileFixture, ResizableFileSync) {
456554 }
457555}
458556
459- TEST_F (MappedFileFixture, Readme) {
460- fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
557+ TEST (MappedFile, Empty) {
558+ fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
559+ EXPECT_FALSE (fs::exists (tmpFile2));
560+ {
561+ size_t maxSize = 4096 ;
562+ resizable_file file (tmpFile2, maxSize);
563+ EXPECT_TRUE (fs::exists (tmpFile2));
564+ EXPECT_EQ (file.size (), 0 );
565+ }
566+ EXPECT_TRUE (fs::exists (tmpFile2));
567+ EXPECT_EQ (fs::file_size (tmpFile2), 0 );
568+ fs::remove (tmpFile2);
569+ EXPECT_FALSE (fs::exists (tmpFile2));
570+ }
571+
572+ TEST_F (MappedFileFixture, ClearExisting) {
573+ EXPECT_EQ (fs::file_size (m_tmpFile), sizeof (int ));
574+ {
575+ size_t maxSize = 4096 ;
576+ resizable_file file (m_tmpFile, maxSize);
577+ EXPECT_EQ (file.size (), sizeof (int ));
578+ file.resize (0 );
579+ }
580+ EXPECT_EQ (fs::file_size (m_tmpFile), 0 );
581+ }
582+
583+ TEST (MappedFile, Readme) {
584+ fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
461585 {
462586 size_t maxSize = 4096 ;
463587 resizable_file file (tmpFile2, maxSize);
@@ -477,58 +601,3 @@ TEST_F(MappedFileFixture, Readme) {
477601 fs::remove (tmpFile2);
478602 EXPECT_FALSE (fs::exists (tmpFile2));
479603}
480-
481- #ifndef _WIN32
482- std::vector<uint8_t > getResidency (void * base, size_t size) {
483- std::vector<unsigned char > result (size / getpagesize (), 0u );
484- int ret = mincore (base, size, result.data ());
485- if (ret != 0 )
486- throw detail::LastError ();
487- return result;
488- }
489-
490- TEST (MappedMemory, PageResidencyAfterDecommit) {
491- const size_t page_size = getpagesize ();
492- const size_t reserve_size = page_size * 64 ; // 64 pages total
493- const size_t commit_size = page_size * 4 ; // We'll use 4 pages
494-
495- // Reserve virtual address space (uncommitted, inaccessible)
496- void * base =
497- mmap (nullptr , reserve_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1 , 0 );
498- ASSERT_NE (base, MAP_FAILED) << " Failed to mmap reserved space" ;
499- EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
500- [](uint8_t c) { return (c & 1u ) == 0 ; }));
501-
502- // Commit a portion with PROT_READ | PROT_WRITE
503- int prot_result = mprotect (base, commit_size, PROT_READ | PROT_WRITE);
504- ASSERT_EQ (prot_result, 0 ) << " Failed to mprotect committed region" ;
505- EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
506- [](uint8_t c) { return (c & 1u ) == 0 ; }));
507-
508- // Touch the memory to ensure it's backed by RAM
509- std::span committed (static_cast <std::byte*>(base), commit_size);
510- std::ranges::fill (committed, std::byte (0xAB ));
511-
512- // Verify pages are resident using mincore
513- EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
514- [](uint8_t c) { return (c & 1u ) == 1 ; }));
515-
516- // Decommit
517- #if 0
518- void* remap = mmap(base, commit_size, PROT_NONE,
519- MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED, -1, 0);
520- ASSERT_EQ(remap, base) << "Failed to remap to decommit pages";
521- #else
522- // See MADV_FREE discussion here: https://github.com/golang/go/issues/42330
523- prot_result = mprotect (base, commit_size, PROT_NONE);
524- ASSERT_EQ (prot_result, 0 ) << " Failed to mprotect committed region back to PROT_NONE" ;
525- int madvise_result = madvise (base, commit_size, MADV_DONTNEED);
526- ASSERT_EQ (madvise_result, 0 ) << " Failed to release pages with madvise" ;
527- #endif
528- EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
529- [](uint8_t c) { return (c & 1u ) == 0 ; }));
530-
531- // Cleanup
532- munmap (base, reserve_size);
533- }
534- #endif
0 commit comments