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