1/////////////////////////////////////////////////////////////////////////////// 2// Name: tests/streams/largefile.cpp 3// Purpose: Tests for large file support 4// Author: Mike Wetherell 5// RCS-ID: $Id: largefile.cpp 63301 2010-01-28 21:38:27Z MW $ 6// Copyright: (c) 2004 Mike Wetherell 7// Licence: wxWidgets licence 8/////////////////////////////////////////////////////////////////////////////// 9 10// 11// We're interested in what happens around offsets 0x7fffffff and 0xffffffff 12// so the test writes a small chunk of test data just before and just after 13// these offsets, then reads them back. 14// 15// The tests can be run with: 16// 17// test --verbose largeFile 18// 19// On systems supporting sparse files they will also be registered in the 20// Streams subsuite so that they run by default. 21// 22 23// For compilers that support precompilation, includes "wx/wx.h". 24#include "testprec.h" 25 26#ifdef __BORLANDC__ 27 #pragma hdrstop 28#endif 29 30// for all others, include the necessary headers 31#ifndef WX_PRECOMP 32 #include "wx/wx.h" 33#endif 34 35#include "wx/filename.h" 36#include "wx/wfstream.h" 37 38#ifdef __WXMSW__ 39#include "wx/msw/wrapwin.h" 40#include <winioctl.h> 41#endif 42 43using std::auto_ptr; 44 45 46/////////////////////////////////////////////////////////////////////////////// 47// Helpers 48 49bool IsFAT(const wxString& path); 50void MakeSparse(const wxString& path, int fd); 51 52 53/////////////////////////////////////////////////////////////////////////////// 54// Base class for the test cases - common code 55 56class LargeFileTest : public CppUnit::TestCase 57{ 58public: 59 LargeFileTest(std::string name) : CppUnit::TestCase(name) { } 60 virtual ~LargeFileTest() { } 61 62protected: 63 void runTest(); 64 65 virtual wxInputStream *MakeInStream(const wxString& name) const = 0; 66 virtual wxOutputStream *MakeOutStream(const wxString& name) const = 0; 67 virtual bool HasLFS() const = 0; 68}; 69 70void LargeFileTest::runTest() 71{ 72 // self deleting temp file 73 struct TmpFile { 74 TmpFile() : m_name(wxFileName::CreateTempFileName(_T("wxlfs-"))) { } 75 ~TmpFile() { if (!m_name.empty()) wxRemoveFile(m_name); } 76 wxString m_name; 77 } tmpfile; 78 79 CPPUNIT_ASSERT(!tmpfile.m_name.empty()); 80 81 bool haveLFS = true; 82 bool fourGigLimit = false; 83 84 if (!HasLFS()) { 85 haveLFS = false; 86 wxString n(getName().c_str(), *wxConvCurrent); 87 wxLogInfo(n + _T(": No large file support, testing up to 2GB only")); 88 } 89 else if (IsFAT(tmpfile.m_name)) { 90 fourGigLimit = true; 91 wxString n(getName().c_str(), *wxConvCurrent); 92 wxLogInfo(n + _T(": FAT volumes are limited to 4GB files")); 93 } 94 95 // size of the test blocks 96 const size_t size = 0x40; 97 98 // the test blocks 99 char upto2Gig[size]; 100 char past2Gig[size]; 101 char upto4Gig[size]; 102 char past4Gig[size]; 103 memset(upto2Gig, 'A', size); 104 memset(past2Gig, 'B', size); 105 memset(upto4Gig, 'X', size); 106 memset(past4Gig, 'Y', size); 107 108 wxFileOffset pos; 109 110 // write a large file 111 { 112 auto_ptr<wxOutputStream> out(MakeOutStream(tmpfile.m_name)); 113 114 // write 'A's at [ 0x7fffffbf, 0x7fffffff [ 115 pos = 0x7fffffff - size; 116 CPPUNIT_ASSERT(out->SeekO(pos) == pos); 117 CPPUNIT_ASSERT(out->Write(upto2Gig, size).LastWrite() == size); 118 pos += size; 119 120 if (haveLFS) { 121 // write 'B's at [ 0x7fffffff, 0x8000003f [ 122 CPPUNIT_ASSERT(out->Write(past2Gig, size).LastWrite() == size); 123 pos += size; 124 CPPUNIT_ASSERT(out->TellO() == pos); 125 126 // write 'X's at [ 0xffffffbf, 0xffffffff [ 127 pos = 0xffffffff - size; 128 CPPUNIT_ASSERT(out->SeekO(pos) == pos); 129 CPPUNIT_ASSERT(out->Write(upto4Gig, size).LastWrite() == size); 130 pos += size; 131 132 if (!fourGigLimit) { 133 // write 'Y's at [ 0xffffffff, 0x10000003f [ 134 CPPUNIT_ASSERT(out->Write(past4Gig, size).LastWrite() == size); 135 pos += size; 136 } 137 } 138 139 // check the file seems to be the right length 140 CPPUNIT_ASSERT(out->TellO() == pos); 141 CPPUNIT_ASSERT(out->GetLength() == pos); 142 } 143 144 // read the large file back 145 { 146 auto_ptr<wxInputStream> in(MakeInStream(tmpfile.m_name)); 147 char buf[size]; 148 149 if (haveLFS) { 150 CPPUNIT_ASSERT(in->GetLength() == pos); 151 pos = 0xffffffff; 152 153 if (!fourGigLimit) { 154 CPPUNIT_ASSERT(in->GetLength() > pos); 155 156 // read back the 'Y's at [ 0xffffffff, 0x10000003f [ 157 CPPUNIT_ASSERT(in->SeekI(pos) == pos); 158 CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size); 159 CPPUNIT_ASSERT(memcmp(buf, past4Gig, size) == 0); 160 161 CPPUNIT_ASSERT(in->TellI() == in->GetLength()); 162 } 163 164 // read back the 'X's at [ 0xffffffbf, 0xffffffff [ 165 pos -= size; 166 CPPUNIT_ASSERT(in->SeekI(pos) == pos); 167 CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size); 168 CPPUNIT_ASSERT(memcmp(buf, upto4Gig, size) == 0); 169 pos += size; 170 CPPUNIT_ASSERT(in->TellI() == pos); 171 172 // read back the 'B's at [ 0x7fffffff, 0x8000003f [ 173 pos = 0x7fffffff; 174 CPPUNIT_ASSERT(in->SeekI(pos) == pos); 175 CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size); 176 CPPUNIT_ASSERT(memcmp(buf, past2Gig, size) == 0); 177 } 178 else { 179 CPPUNIT_ASSERT(in->GetLength() == 0x7fffffff); 180 pos = 0x7fffffff; 181 } 182 183 // read back the 'A's at [ 0x7fffffbf, 0x7fffffff [ 184 pos -= size; 185 CPPUNIT_ASSERT(in->SeekI(pos) == pos); 186 CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size); 187 CPPUNIT_ASSERT(memcmp(buf, upto2Gig, size) == 0); 188 pos += size; 189 CPPUNIT_ASSERT(in->TellI() == pos); 190 } 191} 192 193 194/////////////////////////////////////////////////////////////////////////////// 195// wxFile test case 196 197class LargeFileTest_wxFile : public LargeFileTest 198{ 199public: 200 LargeFileTest_wxFile() : LargeFileTest("wxFile streams") { } 201 202protected: 203 wxInputStream *MakeInStream(const wxString& name) const; 204 wxOutputStream *MakeOutStream(const wxString& name) const; 205 bool HasLFS() const { return (wxFileOffset)0xffffffff > 0; } 206}; 207 208wxInputStream *LargeFileTest_wxFile::MakeInStream(const wxString& name) const 209{ 210 auto_ptr<wxFileInputStream> in(new wxFileInputStream(name)); 211 CPPUNIT_ASSERT(in->Ok()); 212 return in.release(); 213} 214 215wxOutputStream *LargeFileTest_wxFile::MakeOutStream(const wxString& name) const 216{ 217 wxFile file(name, wxFile::write); 218 CPPUNIT_ASSERT(file.IsOpened()); 219 int fd = file.fd(); 220 file.Detach(); 221 MakeSparse(name, fd); 222 return new wxFileOutputStream(fd); 223} 224 225 226/////////////////////////////////////////////////////////////////////////////// 227// wxFFile test case 228 229class LargeFileTest_wxFFile : public LargeFileTest 230{ 231public: 232 LargeFileTest_wxFFile() : LargeFileTest("wxFFile streams") { } 233 234protected: 235 wxInputStream *MakeInStream(const wxString& name) const; 236 wxOutputStream *MakeOutStream(const wxString& name) const; 237 bool HasLFS() const; 238}; 239 240wxInputStream *LargeFileTest_wxFFile::MakeInStream(const wxString& name) const 241{ 242 auto_ptr<wxFFileInputStream> in(new wxFFileInputStream(name)); 243 CPPUNIT_ASSERT(in->Ok()); 244 return in.release(); 245} 246 247wxOutputStream *LargeFileTest_wxFFile::MakeOutStream(const wxString& name) const 248{ 249 wxFFile file(name, _T("w")); 250 CPPUNIT_ASSERT(file.IsOpened()); 251 FILE *fp = file.fp(); 252 file.Detach(); 253 MakeSparse(name, fileno(fp)); 254 return new wxFFileOutputStream(fp); 255} 256 257bool LargeFileTest_wxFFile::HasLFS() const 258{ 259#ifdef wxHAS_LARGE_FFILES 260 return true; 261#else 262 return false; 263#endif 264} 265 266 267/////////////////////////////////////////////////////////////////////////////// 268// The suite 269 270class largeFile : public CppUnit::TestSuite 271{ 272public: 273 largeFile() : CppUnit::TestSuite("largeFile") { } 274 275 static CppUnit::Test *suite(); 276}; 277 278CppUnit::Test *largeFile::suite() 279{ 280 largeFile *suite = new largeFile; 281 282 suite->addTest(new LargeFileTest_wxFile); 283 suite->addTest(new LargeFileTest_wxFFile); 284 285 return suite; 286} 287 288 289/////////////////////////////////////////////////////////////////////////////// 290// Implement the helpers 291// 292// Ideally these tests will be part of the default suite so that regressions 293// are picked up. However this is only possible when sparse files are 294// supported otherwise the tests require too much disk space. 295 296#ifdef __WXMSW__ 297 298#ifndef FILE_SUPPORTS_SPARSE_FILES 299#define FILE_SUPPORTS_SPARSE_FILES 0x00000040 300#endif 301 302#ifndef FSCTL_SET_SPARSE 303 304# ifndef FILE_SPECIAL_ACCESS 305# define FILE_SPECIAL_ACCESS FILE_ANY_ACCESS 306# endif 307# define FSCTL_SET_SPARSE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 49, \ 308 METHOD_BUFFERED, FILE_SPECIAL_ACCESS) 309#endif 310 311static DWORD volumeFlags; 312static wxChar volumeType[64]; 313static bool volumeInfoInit; 314 315void GetVolumeInfo(const wxString& path) 316{ 317 // extract the volume 'C:\' or '\\tooter\share\' from the path 318 wxString vol; 319 320 if (path.substr(1, 2) == _T(":\\")) { 321 vol = path.substr(0, 3); 322 } else { 323 if (path.substr(0, 2) == _T("\\\\")) { 324 size_t i = path.find(_T('\\'), 2); 325 326 if (i != wxString::npos && i > 2) { 327 size_t j = path.find(_T('\\'), ++i); 328 329 if (j != i) 330 vol = path.substr(0, j) + _T("\\"); 331 } 332 } 333 } 334 335 // NULL means the current volume 336 const wxChar *pVol = vol.empty() ? NULL : vol.c_str(); 337 338 if (!::GetVolumeInformation(pVol, NULL, 0, NULL, NULL, 339 &volumeFlags, 340 volumeType, 341 WXSIZEOF(volumeType))) 342 wxLogSysError(_T("GetVolumeInformation() failed")); 343 344 volumeInfoInit = true; 345} 346 347bool IsFAT(const wxString& path) 348{ 349 if (!volumeInfoInit) 350 GetVolumeInfo(path); 351 return wxString(volumeType).Upper().find(_T("FAT")) != wxString::npos; 352} 353 354void MakeSparse(const wxString& path, int fd) 355{ 356 DWORD cb; 357 358 if (!volumeInfoInit) 359 GetVolumeInfo(path); 360 361 if ((volumeFlags & FILE_SUPPORTS_SPARSE_FILES) != 0) 362 if (!::DeviceIoControl((HANDLE)_get_osfhandle(fd), 363 FSCTL_SET_SPARSE, 364 NULL, 0, NULL, 0, &cb, NULL)) 365 volumeFlags &= ~FILE_SUPPORTS_SPARSE_FILES; 366} 367 368// return the suite if sparse files are supported, otherwise return NULL 369// 370CppUnit::Test* GetlargeFileSuite() 371{ 372 if (!volumeInfoInit) { 373 wxString path; 374 { 375 wxFile file; 376 path = wxFileName::CreateTempFileName(wxT("wxlfs-"), &file); 377 MakeSparse(path, file.fd()); 378 } 379 wxRemoveFile(path); 380 } 381 382 if ((volumeFlags & FILE_SUPPORTS_SPARSE_FILES) != 0) 383 return largeFile::suite(); 384 else 385 return NULL; 386} 387 388#else // __WXMSW__ 389 390bool IsFAT(const wxString& WXUNUSED(path)) { return false; } 391void MakeSparse(const wxString& WXUNUSED(path), int WXUNUSED(fd)) { } 392 393// return the suite if sparse files are supported, otherwise return NULL 394// 395CppUnit::Test* GetlargeFileSuite() 396{ 397 wxString path; 398 struct stat st1, st2; 399 memset(&st1, 0, sizeof(st1)); 400 memset(&st2, 0, sizeof(st2)); 401 402 { 403 wxFile file; 404 path = wxFileName::CreateTempFileName(wxT("wxlfs-"), &file); 405 406 fstat(file.fd(), &st1); 407 file.Seek(st1.st_blksize); 408 file.Write("x", 1); 409 fstat(file.fd(), &st1); 410 411 file.Seek(0); 412 file.Write("x", 1); 413 fstat(file.fd(), &st2); 414 } 415 416 wxRemoveFile(path); 417 418 if (st1.st_blocks != st2.st_blocks) 419 return largeFile::suite(); 420 else 421 return NULL; 422} 423 424#endif // __WXMSW__ 425 426CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(largeFile, "largeFile"); 427CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(largeFile, "Streams.largeFile"); 428