@@ -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,78 @@ 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<uint8_t > 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+ EXPECT_EQ (fs::file_size (m_tmpFile), sizeof (int ));
284+ size_t newSize = 1024 * 1024 * 1024 ;
285+ detail::FileDescriptor fd (m_tmpFile, O_RDWR);
286+ detail::MemoryMap<PROT_READ | PROT_WRITE> mapped (nullptr , newSize, MAP_SHARED, fd, 0 );
287+ fd.truncate (newSize);
288+ // First page is already resident, but at least the rest should be untouched
289+ EXPECT_TRUE (
290+ std::ranges::all_of (getResidency (mapped.address (getpagesize ()), newSize - getpagesize ()),
291+ [](uint8_t c) { return (c & 1u ) == 0 ; }));
292+ std::ranges::fill (std::span (reinterpret_cast <uint8_t *>(mapped.address ()), newSize),
293+ uint8_t (0xff ));
294+ EXPECT_TRUE (std::ranges::all_of (getResidency (mapped.address (), newSize),
295+ [](uint8_t c) { return (c & 1u ) == 1 ; }));
296+ fd.truncate (0 );
297+ EXPECT_TRUE (std::ranges::all_of (getResidency (mapped.address (), newSize),
298+ [](uint8_t c) { return (c & 1u ) == 0 ; }));
299+ EXPECT_EQ (fs::file_size (m_tmpFile), 0 );
300+ }
301+
302+ TEST (MappedMemory, LinuxResidencyAfterDecommit) {
303+ const size_t page_size = getpagesize ();
304+ const size_t reserve_size = page_size * 64 ; // 64 pages total
305+ const size_t commit_size = page_size * 4 ; // We'll use 4 pages
306+
307+ // Reserve virtual address space (uncommitted, inaccessible)
308+ void * base =
309+ mmap (nullptr , reserve_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1 , 0 );
310+ ASSERT_NE (base, MAP_FAILED) << " Failed to mmap reserved space" ;
311+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
312+ [](uint8_t c) { return (c & 1u ) == 0 ; }));
313+
314+ // Commit a portion with PROT_READ | PROT_WRITE
315+ int prot_result = mprotect (base, commit_size, PROT_READ | PROT_WRITE);
316+ ASSERT_EQ (prot_result, 0 ) << " Failed to mprotect committed region" ;
317+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
318+ [](uint8_t c) { return (c & 1u ) == 0 ; }));
319+
320+ // Touch the memory to ensure it's backed by RAM
321+ std::span committed (static_cast <std::byte*>(base), commit_size);
322+ std::ranges::fill (committed, std::byte (0xAB ));
323+
324+ // Verify pages are resident using mincore
325+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
326+ [](uint8_t c) { return (c & 1u ) == 1 ; }));
327+
328+ // Decommit
329+ #if 0
330+ void* remap = mmap(base, commit_size, PROT_NONE,
331+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED, -1, 0);
332+ ASSERT_EQ(remap, base) << "Failed to remap to decommit pages";
333+ #else
334+ // See MADV_FREE discussion here: https://github.com/golang/go/issues/42330
335+ prot_result = mprotect (base, commit_size, PROT_NONE);
336+ ASSERT_EQ (prot_result, 0 ) << " Failed to mprotect committed region back to PROT_NONE" ;
337+ int madvise_result = madvise (base, commit_size, MADV_DONTNEED);
338+ ASSERT_EQ (madvise_result, 0 ) << " Failed to release pages with madvise" ;
339+ #endif
340+ EXPECT_TRUE (std::ranges::all_of (getResidency (base, commit_size),
341+ [](uint8_t c) { return (c & 1u ) == 0 ; }));
342+
343+ // Cleanup
344+ munmap (base, reserve_size);
345+ }
256346
257347#endif
258348
@@ -456,8 +546,34 @@ TEST_F(MappedFileFixture, ResizableFileSync) {
456546 }
457547}
458548
459- TEST_F (MappedFileFixture, Readme) {
460- fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
549+ TEST (MappedFile, Empty) {
550+ fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
551+ EXPECT_FALSE (fs::exists (tmpFile2));
552+ {
553+ size_t maxSize = 4096 ;
554+ resizable_file file (tmpFile2, maxSize);
555+ EXPECT_TRUE (fs::exists (tmpFile2));
556+ EXPECT_EQ (file.size (), 0 );
557+ }
558+ EXPECT_TRUE (fs::exists (tmpFile2));
559+ EXPECT_EQ (fs::file_size (tmpFile2), 0 );
560+ fs::remove (tmpFile2);
561+ EXPECT_FALSE (fs::exists (tmpFile2));
562+ }
563+
564+ TEST_F (MappedFileFixture, ClearExisting) {
565+ EXPECT_EQ (fs::file_size (m_tmpFile), sizeof (int ));
566+ {
567+ size_t maxSize = 4096 ;
568+ resizable_file file (m_tmpFile, maxSize);
569+ EXPECT_EQ (file.size (), sizeof (int ));
570+ file.resize (0 );
571+ }
572+ EXPECT_EQ (fs::file_size (m_tmpFile), 0 );
573+ }
574+
575+ TEST (MappedFile, Readme) {
576+ fs::path tmpFile2 = fs::path{testing::TempDir ()} / " test2.dat" ;
461577 {
462578 size_t maxSize = 4096 ;
463579 resizable_file file (tmpFile2, maxSize);
@@ -477,58 +593,3 @@ TEST_F(MappedFileFixture, Readme) {
477593 fs::remove (tmpFile2);
478594 EXPECT_FALSE (fs::exists (tmpFile2));
479595}
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