]> git.saurik.com Git - wxWidgets.git/blob - tests/archive/archivetest.cpp
Fixes to FIXME about stock IDs usage.
[wxWidgets.git] / tests / archive / archivetest.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: tests/archive/archive.cpp
3 // Purpose: Test the archive classes
4 // Author: Mike Wetherell
5 // RCS-ID: $Id$
6 // Copyright: (c) 2004 Mike Wetherell
7 // Licence: wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
9
10 #include "testprec.h"
11
12 #ifdef __BORLANDC__
13 # pragma hdrstop
14 #endif
15
16 #ifndef WX_PRECOMP
17 # include "wx/wx.h"
18 #endif
19
20 #if wxUSE_STREAMS
21
22 #define WX_TEST_ARCHIVE_ITERATOR
23
24 // VC++ 6 warns that the list iterator's '->' operator will not work whenever
25 // std::list is used with a non-pointer, so switch it off.
26 #if defined _MSC_VER && _MSC_VER < 1300
27 #pragma warning (disable:4284)
28 #endif
29
30 #include "wx/zipstrm.h"
31 #include "wx/mstream.h"
32 #include "wx/wfstream.h"
33 #include "wx/dir.h"
34 #include <string>
35 #include <list>
36 #include <map>
37 #include <sys/stat.h>
38
39 using std::string;
40 using std::auto_ptr;
41
42
43 // Check whether member templates can be used
44 //
45 #if defined __GNUC__ && \
46 (__GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95))
47 # define WXARC_MEMBER_TEMPLATES
48 #endif
49 #if defined _MSC_VER && _MSC_VER >= 1310
50 # define WXARC_MEMBER_TEMPLATES
51 #endif
52 #if defined __BORLANDC__ && __BORLANDC__ >= 0x530
53 # define WXARC_MEMBER_TEMPLATES
54 #endif
55 #if defined __DMC__ && __DMC__ >= 0x832
56 # define WXARC_MEMBER_TEMPLATES
57 #endif
58 #if defined __MWERKS__ && __MWERKS__ >= 0x2200
59 # define WXARC_MEMBER_TEMPLATES
60 #endif
61 #if defined __HP_aCC && __HP_aCC > 33300
62 # define WXARC_MEMBER_TEMPLATES
63 #endif
64 #if defined __SUNPRO_CC && __SUNPRO_CC > 0x500
65 # define WXARC_MEMBER_TEMPLATES
66 #endif
67
68
69 ///////////////////////////////////////////////////////////////////////////////
70 // Bit flags for options for the tests
71
72 enum Options
73 {
74 PipeIn = 0x01, // input streams are non-seekable
75 PipeOut = 0x02, // output streams are non-seekable
76 Stub = 0x04, // the archive should be appended to a stub
77 AllOptions = 0x07
78 };
79
80
81 ///////////////////////////////////////////////////////////////////////////////
82 // These structs are passed as the template parameter of the test case to
83 // specify a set of classes to use in the test. This allows either the generic
84 // wxArchiveXXX interface to be exercised or the specific interface for a
85 // particular archive format e.g. wxZipXXX.
86
87 struct ArchiveClasses
88 {
89 typedef wxArchiveEntry EntryT;
90 typedef wxArchiveInputStream InputStreamT;
91 typedef wxArchiveOutputStream OutputStreamT;
92 typedef wxArchiveClassFactory ClassFactoryT;
93 typedef wxArchiveNotifier NotifierT;
94 typedef wxArchiveIter IterT;
95 typedef wxArchivePairIter PairIterT;
96 };
97
98 struct ZipClasses
99 {
100 typedef wxZipEntry EntryT;
101 typedef wxZipInputStream InputStreamT;
102 typedef wxZipOutputStream OutputStreamT;
103 typedef wxZipClassFactory ClassFactoryT;
104 typedef wxZipNotifier NotifierT;
105 typedef wxZipIter IterT;
106 typedef wxZipPairIter PairIterT;
107 };
108
109
110 ///////////////////////////////////////////////////////////////////////////////
111 // A class to hold a test entry
112
113 class TestEntry
114 {
115 public:
116 TestEntry(const wxDateTime& dt, int len, const char *data);
117 ~TestEntry() { delete [] m_data; }
118
119 wxDateTime GetDateTime() const { return m_dt; }
120 wxFileOffset GetLength() const { return m_len; }
121 size_t GetSize() const { return m_len; }
122 const char *GetData() const { return m_data; }
123 wxString GetComment() const { return m_comment; }
124 bool IsText() const { return m_isText; }
125
126 void SetComment(const wxString& comment) { m_comment = comment; }
127 void SetDateTime(const wxDateTime& dt) { m_dt = dt; }
128
129 private:
130 wxDateTime m_dt;
131 size_t m_len;
132 char *m_data;
133 wxString m_comment;
134 bool m_isText;
135 };
136
137 TestEntry::TestEntry(const wxDateTime& dt, int len, const char *data)
138 : m_dt(dt),
139 m_len(len),
140 m_isText(len > 0)
141 {
142 m_data = new char[len];
143 memcpy(m_data, data, len);
144
145 for (int i = 0; i < len && m_isText; i++)
146 m_isText = (signed char)m_data[i] > 0;
147 }
148
149
150 ///////////////////////////////////////////////////////////////////////////////
151 // TestOutputStream and TestInputStream are memory streams which can be
152 // seekable or non-seekable.
153
154 class TestOutputStream : public wxOutputStream
155 {
156 public:
157 TestOutputStream(int options);
158
159 ~TestOutputStream() { delete [] m_data; }
160
161 int GetOptions() const { return m_options; }
162 wxFileOffset GetLength() const { return m_size; }
163
164 // gives away the data, this stream is then empty, and can be reused
165 void GetData(char*& data, size_t& size);
166
167 enum { STUB_SIZE = 2048, INITIAL_SIZE = 0x18000, SEEK_LIMIT = 0x100000 };
168
169 private:
170 void Init();
171
172 wxFileOffset OnSysSeek(wxFileOffset pos, wxSeekMode mode);
173 wxFileOffset OnSysTell() const;
174 size_t OnSysWrite(const void *buffer, size_t size);
175
176 int m_options;
177 size_t m_pos;
178 size_t m_capacity;
179 size_t m_size;
180 char *m_data;
181 };
182
183 TestOutputStream::TestOutputStream(int options)
184 : m_options(options)
185 {
186 Init();
187 }
188
189 void TestOutputStream::Init()
190 {
191 m_data = NULL;
192 m_size = 0;
193 m_capacity = 0;
194 m_pos = 0;
195
196 if (m_options & Stub) {
197 wxCharBuffer buf(STUB_SIZE);
198 memset(buf.data(), 0, STUB_SIZE);
199 Write(buf, STUB_SIZE);
200 }
201 }
202
203 wxFileOffset TestOutputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
204 {
205 if ((m_options & PipeOut) == 0) {
206 switch (mode) {
207 case wxFromStart: break;
208 case wxFromCurrent: pos += m_pos; break;
209 case wxFromEnd: pos += m_size; break;
210 }
211 if (pos < 0 || pos > SEEK_LIMIT)
212 return wxInvalidOffset;
213 m_pos = (size_t)pos;
214 return m_pos;
215 }
216 return wxInvalidOffset;
217 }
218
219 wxFileOffset TestOutputStream::OnSysTell() const
220 {
221 return (m_options & PipeOut) == 0 ? (wxFileOffset)m_pos : wxInvalidOffset;
222 }
223
224 size_t TestOutputStream::OnSysWrite(const void *buffer, size_t size)
225 {
226 if (!IsOk() || !size)
227 return 0;
228 m_lasterror = wxSTREAM_WRITE_ERROR;
229
230 size_t newsize = m_pos + size;
231 wxCHECK(newsize > m_pos, 0);
232
233 if (m_capacity < newsize) {
234 size_t capacity = m_capacity ? m_capacity : INITIAL_SIZE;
235
236 while (capacity < newsize) {
237 capacity <<= 1;
238 wxCHECK(capacity > m_capacity, 0);
239 }
240
241 char *buf = new char[capacity];
242 if (m_data)
243 memcpy(buf, m_data, m_capacity);
244 delete [] m_data;
245 m_data = buf;
246 m_capacity = capacity;
247 }
248
249 memcpy(m_data + m_pos, buffer, size);
250 m_pos += size;
251 if (m_pos > m_size)
252 m_size = m_pos;
253 m_lasterror = wxSTREAM_NO_ERROR;
254
255 return size;
256 }
257
258 void TestOutputStream::GetData(char*& data, size_t& size)
259 {
260 data = m_data;
261 size = m_size;
262
263 if (m_options & Stub) {
264 char *d = m_data;
265 size += STUB_SIZE;
266
267 if (size > m_capacity) {
268 d = new char[size];
269 memcpy(d + STUB_SIZE, m_data, m_size);
270 delete [] m_data;
271 }
272 else {
273 memmove(d + STUB_SIZE, d, m_size);
274 }
275
276 memset(d, 0, STUB_SIZE);
277 data = d;
278 }
279
280 Init();
281 Reset();
282 }
283
284 class TestInputStream : public wxInputStream
285 {
286 public:
287 // ctor takes the data from the output stream, which is then empty
288 TestInputStream(TestOutputStream& out) : m_data(NULL) { SetData(out); }
289 // this ctor 'dups'
290 TestInputStream(const TestInputStream& in);
291 ~TestInputStream() { delete [] m_data; }
292
293 void Rewind();
294 wxFileOffset GetLength() const { return m_size; }
295 void SetData(TestOutputStream& out);
296
297 private:
298 wxFileOffset OnSysSeek(wxFileOffset pos, wxSeekMode mode);
299 wxFileOffset OnSysTell() const;
300 size_t OnSysRead(void *buffer, size_t size);
301
302 int m_options;
303 size_t m_pos;
304 size_t m_size;
305 char *m_data;
306 };
307
308 TestInputStream::TestInputStream(const TestInputStream& in)
309 : m_options(in.m_options),
310 m_pos(in.m_pos),
311 m_size(in.m_size)
312 {
313 m_data = new char[m_size];
314 memcpy(m_data, in.m_data, m_size);
315 }
316
317 void TestInputStream::Rewind()
318 {
319 if ((m_options & Stub) && (m_options & PipeIn))
320 m_pos = TestOutputStream::STUB_SIZE * 2;
321 else
322 m_pos = 0;
323
324 if (m_wbacksize) {
325 free(m_wback);
326 m_wback = NULL;
327 m_wbacksize = 0;
328 m_wbackcur = 0;
329 }
330 }
331
332 void TestInputStream::SetData(TestOutputStream& out)
333 {
334 delete [] m_data;
335 m_options = out.GetOptions();
336 out.GetData(m_data, m_size);
337 Rewind();
338 Reset();
339 }
340
341 wxFileOffset TestInputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
342 {
343 if ((m_options & PipeIn) == 0) {
344 switch (mode) {
345 case wxFromStart: break;
346 case wxFromCurrent: pos += m_pos; break;
347 case wxFromEnd: pos += m_size; break;
348 }
349 if (pos < 0 || pos > TestOutputStream::SEEK_LIMIT)
350 return wxInvalidOffset;
351 m_pos = (size_t)pos;
352 return m_pos;
353 }
354 return wxInvalidOffset;
355 }
356
357 wxFileOffset TestInputStream::OnSysTell() const
358 {
359 return (m_options & PipeIn) == 0 ? (wxFileOffset)m_pos : wxInvalidOffset;
360 }
361
362 size_t TestInputStream::OnSysRead(void *buffer, size_t size)
363 {
364 if (!IsOk() || !size)
365 return 0;
366 if (m_size <= m_pos) {
367 m_lasterror = wxSTREAM_EOF;
368 return 0;
369 }
370
371 if (m_size - m_pos < size)
372 size = m_size - m_pos;
373 memcpy(buffer, m_data + m_pos, size);
374 m_pos += size;
375 return size;
376 }
377
378
379 ///////////////////////////////////////////////////////////////////////////////
380 // minimal non-intrusive reference counting pointer for testing the iterators
381
382 template <class T> class Ptr
383 {
384 public:
385 explicit Ptr(T* p = NULL) : m_p(p), m_count(new int) { *m_count = 1; }
386 Ptr(const Ptr& sp) : m_p(sp.m_p), m_count(sp.m_count) { ++*m_count; }
387 ~Ptr() { Free(); }
388
389 Ptr& operator =(const Ptr& sp) {
390 if (&sp != this) {
391 Free();
392 m_p = sp.m_p;
393 m_count = sp.m_count;
394 ++*m_count;
395 }
396 return *this;
397 }
398
399 T* get() const { return m_p; }
400 T* operator->() const { return m_p; }
401 T& operator*() const { return *m_p; }
402
403 private:
404 void Free() {
405 if (--*m_count == 0) {
406 delete m_p;
407 delete m_count;
408 }
409 }
410
411 T *m_p;
412 int *m_count;
413 };
414
415
416 ///////////////////////////////////////////////////////////////////////////////
417 // Clean-up for temp directory
418
419 class TempDir
420 {
421 public:
422 TempDir();
423 ~TempDir();
424 wxString GetName() const { return m_tmp; }
425
426 private:
427 void RemoveDir(wxString& path);
428 wxString m_tmp;
429 wxString m_original;
430 };
431
432 TempDir::TempDir()
433 {
434 wxString tmp = wxFileName::CreateTempFileName(_T("arctest-"));
435 if (tmp != wxEmptyString) {
436 wxRemoveFile(tmp);
437 m_original = wxGetCwd();
438 CPPUNIT_ASSERT(wxMkdir(tmp, 0700));
439 m_tmp = tmp;
440 CPPUNIT_ASSERT(wxSetWorkingDirectory(tmp));
441 }
442 }
443
444 TempDir::~TempDir()
445 {
446 if (m_tmp != wxEmptyString) {
447 wxSetWorkingDirectory(m_original);
448 RemoveDir(m_tmp);
449 }
450 }
451
452 void TempDir::RemoveDir(wxString& path)
453 {
454 wxCHECK_RET(!m_tmp.empty() && path.substr(0, m_tmp.length()) == m_tmp,
455 _T("remove '") + path + _T("' fails safety check"));
456
457 const wxChar *files[] = {
458 _T("text/empty"),
459 _T("text/small"),
460 _T("bin/bin1000"),
461 _T("bin/bin4095"),
462 _T("bin/bin4096"),
463 _T("bin/bin4097"),
464 _T("bin/bin16384"),
465 _T("zero/zero5"),
466 _T("zero/zero1024"),
467 _T("zero/zero32768"),
468 _T("zero/zero16385"),
469 _T("zero/newname"),
470 _T("newfile"),
471 };
472
473 const wxChar *dirs[] = {
474 _T("text/"), _T("bin/"), _T("zero/"), _T("empty/")
475 };
476
477 wxString tmp = m_tmp + wxFileName::GetPathSeparator();
478 size_t i;
479
480 for (i = 0; i < WXSIZEOF(files); i++)
481 wxRemoveFile(tmp + wxFileName(files[i], wxPATH_UNIX).GetFullPath());
482
483 for (i = 0; i < WXSIZEOF(dirs); i++)
484 wxRmdir(tmp + wxFileName(dirs[i], wxPATH_UNIX).GetFullPath());
485
486 if (!wxRmdir(m_tmp))
487 wxLogSysError(_T("can't remove temporary dir '%s'"), m_tmp.c_str());
488 }
489
490
491 ///////////////////////////////////////////////////////////////////////////////
492 // wxFFile streams for piping to/from an external program
493
494 #if defined __UNIX__ || defined __MINGW32__
495 # define WXARC_popen popen
496 # define WXARC_pclose pclose
497 #elif defined _MSC_VER || defined __BORLANDC__
498 # define WXARC_popen _popen
499 # define WXARC_pclose _pclose
500 #else
501 # define WXARC_NO_POPEN
502 # define WXARC_popen(cmd, type) NULL
503 # define WXARC_pclose(fp)
504 #endif
505
506 #ifdef __WXMSW__
507 # define WXARC_b "b"
508 #else
509 # define WXARC_b
510 #endif
511
512 class PFileInputStream : public wxFFileInputStream
513 {
514 public:
515 PFileInputStream(const wxString& cmd) :
516 wxFFileInputStream(WXARC_popen(cmd.mb_str(), "r" WXARC_b)) { }
517 ~PFileInputStream()
518 { WXARC_pclose(m_file->fp()); m_file->Detach(); }
519 };
520
521 class PFileOutputStream : public wxFFileOutputStream
522 {
523 public:
524 PFileOutputStream(const wxString& cmd) :
525 wxFFileOutputStream(WXARC_popen(cmd.mb_str(), "w" WXARC_b)) { }
526 ~PFileOutputStream()
527 { WXARC_pclose(m_file->fp()); m_file->Detach(); }
528 };
529
530
531 ///////////////////////////////////////////////////////////////////////////////
532 // The test case
533
534 template <class Classes>
535 class ArchiveTestCase : public CppUnit::TestCase
536 {
537 public:
538 ArchiveTestCase(string name,
539 int id,
540 wxArchiveClassFactory *factory,
541 int options,
542 const wxString& archiver = wxEmptyString,
543 const wxString& unarchiver = wxEmptyString);
544
545 ~ArchiveTestCase();
546
547 protected:
548 // the classes to test
549 typedef typename Classes::EntryT EntryT;
550 typedef typename Classes::InputStreamT InputStreamT;
551 typedef typename Classes::OutputStreamT OutputStreamT;
552 typedef typename Classes::ClassFactoryT ClassFactoryT;
553 typedef typename Classes::NotifierT NotifierT;
554 typedef typename Classes::IterT IterT;
555 typedef typename Classes::PairIterT PairIterT;
556
557 // the entry point for the test
558 void runTest();
559
560 // create the test data
561 void CreateTestData();
562 TestEntry& Add(const char *name, const char *data, int len = -1);
563 TestEntry& Add(const char *name, int len = 0, int value = EOF);
564
565 // 'archive up' the test data
566 void CreateArchive(wxOutputStream& out);
567 void CreateArchive(wxOutputStream& out, const wxString& archiver);
568
569 // perform various modifications on the archive
570 void ModifyArchive(wxInputStream& in, wxOutputStream& out);
571
572 // extract the archive and verify its contents
573 void ExtractArchive(wxInputStream& in);
574 void ExtractArchive(wxInputStream& in, const wxString& unarchiver);
575 void VerifyDir(wxString& path, size_t rootlen = 0);
576
577 // tests for the iterators
578 void TestIterator(wxInputStream& in);
579 void TestPairIterator(wxInputStream& in);
580 void TestSmartIterator(wxInputStream& in);
581 void TestSmartPairIterator(wxInputStream& in);
582
583 // try reading two entries at the same time
584 void ReadSimultaneous(TestInputStream& in);
585
586 // overridables
587 virtual void OnCreateArchive(OutputStreamT& WXUNUSED(arc)) { }
588 virtual void OnSetNotifier(EntryT& entry);
589
590 virtual void OnArchiveExtracted(InputStreamT& WXUNUSED(arc),
591 int WXUNUSED(expectedTotal)) { }
592
593 virtual void OnCreateEntry( OutputStreamT& WXUNUSED(arc),
594 TestEntry& WXUNUSED(testEntry),
595 EntryT *entry = NULL) { (void)entry; }
596
597 virtual void OnEntryExtracted( EntryT& WXUNUSED(entry),
598 const TestEntry& WXUNUSED(testEntry),
599 InputStreamT *arc = NULL) { (void)arc; }
600
601 typedef std::map<wxString, TestEntry*> TestEntries;
602 TestEntries m_testEntries; // test data
603 auto_ptr<ClassFactoryT> m_factory; // factory to make classes
604 int m_options; // test options
605 wxDateTime m_timeStamp; // timestamp to give test entries
606 int m_id; // select between the possibilites
607 wxString m_archiver; // external archiver
608 wxString m_unarchiver; // external unarchiver
609 };
610
611 // Constructor
612 // The only way I could get this to compile on VC++ 5.0 was to pass 'factory'
613 // as a wxArchiveFactory* then cast it, even then only with some ifdefing.
614 //
615 template <class Classes>
616 ArchiveTestCase<Classes>::ArchiveTestCase(
617 string name,
618 int id,
619 wxArchiveClassFactory *factory,
620 int options,
621 const wxString& archiver,
622 const wxString& unarchiver)
623 :
624 CppUnit::TestCase(name),
625 #if defined _MSC_VER && _MSC_VER < 1300
626 m_factory(dynamic_cast<Classes::ClassFactoryT*>(factory)),
627 #else
628 m_factory(dynamic_cast<typename Classes::ClassFactoryT*>(factory)),
629 #endif
630 m_options(options),
631 m_timeStamp(1, wxDateTime::Mar, 2005, 12, 0),
632 m_id(id),
633 m_archiver(archiver),
634 m_unarchiver(unarchiver)
635 {
636 wxASSERT(m_factory.get() != NULL);
637 }
638
639 template <class Classes>
640 ArchiveTestCase<Classes>::~ArchiveTestCase()
641 {
642 TestEntries::iterator it;
643 for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it)
644 delete it->second;
645 }
646
647 template <class Classes>
648 void ArchiveTestCase<Classes>::runTest()
649 {
650 TestOutputStream out(m_options);
651
652 CreateTestData();
653
654 if (m_archiver.empty())
655 CreateArchive(out);
656 else
657 CreateArchive(out, m_archiver);
658
659 // check archive could be created
660 CPPUNIT_ASSERT(out.GetLength() > 0);
661
662 TestInputStream in(out);
663
664 TestIterator(in);
665 in.Rewind();
666 TestPairIterator(in);
667 in.Rewind();
668 TestSmartIterator(in);
669 in.Rewind();
670 TestSmartPairIterator(in);
671 in.Rewind();
672
673 if ((m_options & PipeIn) == 0) {
674 ReadSimultaneous(in);
675 in.Rewind();
676 }
677
678 ModifyArchive(in, out);
679 in.SetData(out);
680
681 if (m_unarchiver.empty())
682 ExtractArchive(in);
683 else
684 ExtractArchive(in, m_unarchiver);
685
686 // check that all the test entries were found in the archive
687 CPPUNIT_ASSERT(m_testEntries.empty());
688 }
689
690 template <class Classes>
691 void ArchiveTestCase<Classes>::CreateTestData()
692 {
693 Add("text/");
694 Add("text/empty", "");
695 Add("text/small", "Small text file for testing\n"
696 "archive streams in wxWidgets\n");
697
698 Add("bin/");
699 Add("bin/bin1000", 1000);
700 Add("bin/bin4095", 4095);
701 Add("bin/bin4096", 4096);
702 Add("bin/bin4097", 4097);
703 Add("bin/bin16384", 16384);
704
705 Add("zero/");
706 Add("zero/zero5", 5, 0);
707 Add("zero/zero1024", 1024, 109);
708 Add("zero/zero32768", 32768, 106);
709 Add("zero/zero16385", 16385, 119);
710
711 Add("empty/");
712 }
713
714 template <class Classes>
715 TestEntry& ArchiveTestCase<Classes>::Add(const char *name,
716 const char *data,
717 int len /*=-1*/)
718 {
719 if (len == -1)
720 len = strlen(data);
721 TestEntry*& entry = m_testEntries[wxString(name, *wxConvCurrent)];
722 wxASSERT(entry == NULL);
723 entry = new TestEntry(m_timeStamp, len, data);
724 m_timeStamp += wxTimeSpan(0, 1, 30);
725 return *entry;
726 }
727
728 template <class Classes>
729 TestEntry& ArchiveTestCase<Classes>::Add(const char *name,
730 int len /*=0*/,
731 int value /*=EOF*/)
732 {
733 wxCharBuffer buf(len);
734 for (int i = 0; i < len; i++)
735 buf.data()[i] = (char)(value == EOF ? rand() : value);
736 return Add(name, buf, len);
737 }
738
739 // Create an archive using the wx archive classes, write it to 'out'
740 //
741 template <class Classes>
742 void ArchiveTestCase<Classes>::CreateArchive(wxOutputStream& out)
743 {
744 auto_ptr<OutputStreamT> arc(m_factory->NewStream(out));
745 TestEntries::iterator it;
746
747 OnCreateArchive(*arc);
748
749 // We want to try creating entries in various different ways, 'choices'
750 // is just a number used to select between all the various possibilities.
751 int choices = m_id;
752
753 for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it) {
754 choices += 5;
755 TestEntry& testEntry = *it->second;
756 wxString name = it->first;
757
758 // It should be possible to create a directory entry just by supplying
759 // a name that looks like a directory, or alternatively any old name
760 // can be identified as a directory using SetIsDir or PutNextDirEntry
761 bool setIsDir = name.Last() == _T('/') && (choices & 1);
762 if (setIsDir)
763 name.erase(name.length() - 1);
764
765 // provide some context for the error message so that we know which
766 // iteration of the loop we were on
767 string error_entry((_T(" '") + name + _T("'")).mb_str());
768 string error_context(" failed for entry" + error_entry);
769
770 if ((choices & 2) || testEntry.IsText()) {
771 // try PutNextEntry(EntryT *pEntry)
772 auto_ptr<EntryT> entry(m_factory->NewEntry());
773 entry->SetName(name, wxPATH_UNIX);
774 if (setIsDir)
775 entry->SetIsDir();
776 entry->SetDateTime(testEntry.GetDateTime());
777 entry->SetSize(testEntry.GetLength());
778 OnCreateEntry(*arc, testEntry, entry.get());
779 CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context,
780 arc->PutNextEntry(entry.release()));
781 }
782 else {
783 // try the convenience methods
784 OnCreateEntry(*arc, testEntry);
785 if (setIsDir)
786 CPPUNIT_ASSERT_MESSAGE("PutNextDirEntry" + error_context,
787 arc->PutNextDirEntry(name, testEntry.GetDateTime()));
788 else
789 CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context,
790 arc->PutNextEntry(name, testEntry.GetDateTime(),
791 testEntry.GetLength()));
792 }
793
794 if (name.Last() != _T('/')) {
795 // for non-dirs write the data
796 arc->Write(testEntry.GetData(), testEntry.GetSize());
797 CPPUNIT_ASSERT_MESSAGE("LastWrite check" + error_context,
798 arc->LastWrite() == testEntry.GetSize());
799 // should work with or without explicit CloseEntry
800 if (choices & 3)
801 CPPUNIT_ASSERT_MESSAGE("CloseEntry" + error_context,
802 arc->CloseEntry());
803 }
804
805 CPPUNIT_ASSERT_MESSAGE("IsOk" + error_context, arc->IsOk());
806 }
807
808 // should work with or without explicit Close
809 if (m_id % 2)
810 CPPUNIT_ASSERT(arc->Close());
811 }
812
813 // Create an archive using an external archive program
814 //
815 template <class Classes>
816 void ArchiveTestCase<Classes>::CreateArchive(wxOutputStream& out,
817 const wxString& archiver)
818 {
819 // for an external archiver the test data need to be written to
820 // temp files
821 TempDir tmpdir;
822
823 // write the files
824 TestEntries::iterator i;
825 for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) {
826 wxFileName fn(i->first, wxPATH_UNIX);
827 TestEntry& entry = *i->second;
828
829 if (fn.IsDir()) {
830 fn.Mkdir(0777, wxPATH_MKDIR_FULL);
831 } else {
832 wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);
833 wxFFileOutputStream fileout(fn.GetFullPath());
834 fileout.Write(entry.GetData(), entry.GetSize());
835 }
836 }
837
838 for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) {
839 wxFileName fn(i->first, wxPATH_UNIX);
840 TestEntry& entry = *i->second;
841 wxDateTime dt = entry.GetDateTime();
842 #ifdef __WXMSW__
843 if (fn.IsDir())
844 entry.SetDateTime(wxDateTime());
845 else
846 #endif
847 fn.SetTimes(NULL, &dt, NULL);
848 }
849
850 if ((m_options & PipeOut) == 0) {
851 wxFileName fn(tmpdir.GetName());
852 fn.SetExt(_T("arc"));
853 wxString tmparc = fn.GetFullPath();
854
855 // call the archiver to create an archive file
856 system(wxString::Format(archiver, tmparc.c_str()).mb_str());
857
858 // then load the archive file
859 {
860 wxFFileInputStream in(tmparc);
861 if (in.Ok())
862 out.Write(in);
863 }
864
865 wxRemoveFile(tmparc);
866 }
867 else {
868 // for the non-seekable test, have the archiver output to "-"
869 // and read the archive via a pipe
870 PFileInputStream in(wxString::Format(archiver, _T("-")));
871 if (in.Ok())
872 out.Write(in);
873 }
874 }
875
876 // Do a standard set of modification on an archive, delete an entry,
877 // rename an entry and add an entry
878 //
879 template <class Classes>
880 void ArchiveTestCase<Classes>::ModifyArchive(wxInputStream& in,
881 wxOutputStream& out)
882 {
883 auto_ptr<InputStreamT> arcIn(m_factory->NewStream(in));
884 auto_ptr<OutputStreamT> arcOut(m_factory->NewStream(out));
885 EntryT *pEntry;
886
887 const wxString deleteName = _T("bin/bin1000");
888 const wxString renameFrom = _T("zero/zero1024");
889 const wxString renameTo = _T("zero/newname");
890 const wxString newName = _T("newfile");
891 const char *newData = "New file added as a test\n";
892
893 arcOut->CopyArchiveMetaData(*arcIn);
894
895 while ((pEntry = arcIn->GetNextEntry()) != NULL) {
896 auto_ptr<EntryT> entry(pEntry);
897 OnSetNotifier(*entry);
898 wxString name = entry->GetName(wxPATH_UNIX);
899
900 // provide some context for the error message so that we know which
901 // iteration of the loop we were on
902 string error_entry((_T(" '") + name + _T("'")).mb_str());
903 string error_context(" failed for entry" + error_entry);
904
905 if (name == deleteName) {
906 TestEntries::iterator it = m_testEntries.find(name);
907 CPPUNIT_ASSERT_MESSAGE(
908 "deletion failed (already deleted?) for" + error_entry,
909 it != m_testEntries.end());
910 TestEntry *p = it->second;
911 m_testEntries.erase(it);
912 delete p;
913 }
914 else {
915 if (name == renameFrom) {
916 entry->SetName(renameTo);
917 TestEntries::iterator it = m_testEntries.find(renameFrom);
918 CPPUNIT_ASSERT_MESSAGE(
919 "rename failed (already renamed?) for" + error_entry,
920 it != m_testEntries.end());
921 TestEntry *p = it->second;
922 m_testEntries.erase(it);
923 m_testEntries[renameTo] = p;
924 }
925
926 CPPUNIT_ASSERT_MESSAGE("CopyEntry" + error_context,
927 arcOut->CopyEntry(entry.release(), *arcIn));
928 }
929 }
930
931 // check that the deletion and rename were done
932 CPPUNIT_ASSERT(m_testEntries.count(deleteName) == 0);
933 CPPUNIT_ASSERT(m_testEntries.count(renameFrom) == 0);
934 CPPUNIT_ASSERT(m_testEntries.count(renameTo) == 1);
935
936 // check that the end of the input archive was reached without error
937 CPPUNIT_ASSERT(arcIn->Eof());
938
939 // try adding a new entry
940 TestEntry& testEntry = Add(newName.mb_str(), newData);
941 auto_ptr<EntryT> newentry(m_factory->NewEntry());
942 newentry->SetName(newName);
943 newentry->SetDateTime(testEntry.GetDateTime());
944 newentry->SetSize(testEntry.GetLength());
945 OnCreateEntry(*arcOut, testEntry, newentry.get());
946 OnSetNotifier(*newentry);
947 CPPUNIT_ASSERT(arcOut->PutNextEntry(newentry.release()));
948 CPPUNIT_ASSERT(arcOut->Write(newData, strlen(newData)).IsOk());
949
950 // should work with or without explicit Close
951 if (m_id % 2)
952 CPPUNIT_ASSERT(arcOut->Close());
953 }
954
955 // Extract an archive using the wx archive classes
956 //
957 template <class Classes>
958 void ArchiveTestCase<Classes>::ExtractArchive(wxInputStream& in)
959 {
960 typedef Ptr<EntryT> EntryPtr;
961 typedef std::list<EntryPtr> Entries;
962 typedef typename Entries::iterator EntryIter;
963
964 auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
965 int expectedTotal = m_testEntries.size();
966 EntryPtr entry;
967 Entries entries;
968
969 if ((m_options & PipeIn) == 0)
970 OnArchiveExtracted(*arc, expectedTotal);
971
972 while (entry = EntryPtr(arc->GetNextEntry()), entry.get() != NULL) {
973 wxString name = entry->GetName(wxPATH_UNIX);
974
975 // provide some context for the error message so that we know which
976 // iteration of the loop we were on
977 string error_entry((_T(" '") + name + _T("'")).mb_str());
978 string error_context(" failed for entry" + error_entry);
979
980 TestEntries::iterator it = m_testEntries.find(name);
981 CPPUNIT_ASSERT_MESSAGE(
982 "archive contains an entry that shouldn't be there" + error_entry,
983 it != m_testEntries.end());
984
985 const TestEntry& testEntry = *it->second;
986
987 wxDateTime dt = testEntry.GetDateTime();
988 if (dt.IsValid())
989 CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context,
990 dt == entry->GetDateTime());
991
992 // non-seekable entries are allowed to have GetSize == wxInvalidOffset
993 // until the end of the entry's data has been read past
994 CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context,
995 testEntry.GetLength() == entry->GetSize() ||
996 ((m_options & PipeIn) != 0 && entry->GetSize() == wxInvalidOffset));
997 CPPUNIT_ASSERT_MESSAGE(
998 "arc->GetLength() == entry->GetSize()" + error_context,
999 arc->GetLength() == entry->GetSize());
1000
1001 if (name.Last() != _T('/'))
1002 {
1003 CPPUNIT_ASSERT_MESSAGE("!IsDir" + error_context,
1004 !entry->IsDir());
1005 wxCharBuffer buf(testEntry.GetSize() + 1);
1006 CPPUNIT_ASSERT_MESSAGE("Read until Eof" + error_context,
1007 arc->Read(buf.data(), testEntry.GetSize() + 1).Eof());
1008 CPPUNIT_ASSERT_MESSAGE("LastRead check" + error_context,
1009 arc->LastRead() == testEntry.GetSize());
1010 CPPUNIT_ASSERT_MESSAGE("data compare" + error_context,
1011 !memcmp(buf.data(), testEntry.GetData(), testEntry.GetSize()));
1012 } else {
1013 CPPUNIT_ASSERT_MESSAGE("IsDir" + error_context, entry->IsDir());
1014 }
1015
1016 // GetSize() must return the right result in all cases after all the
1017 // data has been read
1018 CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context,
1019 testEntry.GetLength() == entry->GetSize());
1020 CPPUNIT_ASSERT_MESSAGE(
1021 "arc->GetLength() == entry->GetSize()" + error_context,
1022 arc->GetLength() == entry->GetSize());
1023
1024 if ((m_options & PipeIn) == 0) {
1025 OnEntryExtracted(*entry, testEntry, arc.get());
1026 delete it->second;
1027 m_testEntries.erase(it);
1028 } else {
1029 entries.push_back(entry);
1030 }
1031 }
1032
1033 // check that the end of the input archive was reached without error
1034 CPPUNIT_ASSERT(arc->Eof());
1035
1036 // for non-seekable streams these data are only guaranteed to be
1037 // available once the end of the archive has been reached
1038 if (m_options & PipeIn) {
1039 for (EntryIter i = entries.begin(); i != entries.end(); ++i) {
1040 wxString name = (*i)->GetName(wxPATH_UNIX);
1041 TestEntries::iterator j = m_testEntries.find(name);
1042 OnEntryExtracted(**i, *j->second);
1043 delete j->second;
1044 m_testEntries.erase(j);
1045 }
1046 OnArchiveExtracted(*arc, expectedTotal);
1047 }
1048 }
1049
1050 // Extract an archive using an external unarchive program
1051 //
1052 template <class Classes>
1053 void ArchiveTestCase<Classes>::ExtractArchive(wxInputStream& in,
1054 const wxString& unarchiver)
1055 {
1056 // for an external unarchiver, unarchive to a tempdir
1057 TempDir tmpdir;
1058
1059 if ((m_options & PipeIn) == 0) {
1060 wxFileName fn(tmpdir.GetName());
1061 fn.SetExt(_T("arc"));
1062 wxString tmparc = fn.GetFullPath();
1063
1064 if (m_options & Stub)
1065 in.SeekI(TestOutputStream::STUB_SIZE * 2);
1066
1067 // write the archive to a temporary file
1068 {
1069 wxFFileOutputStream out(tmparc);
1070 if (out.Ok())
1071 out.Write(in);
1072 }
1073
1074 // call unarchiver
1075 system(wxString::Format(unarchiver, tmparc.c_str()).mb_str());
1076 wxRemoveFile(tmparc);
1077 }
1078 else {
1079 // for the non-seekable test, have the archiver extract "-" and
1080 // feed it the archive via a pipe
1081 PFileOutputStream out(wxString::Format(unarchiver, _T("-")));
1082 if (out.Ok())
1083 out.Write(in);
1084 }
1085
1086 wxString dir = tmpdir.GetName();
1087 VerifyDir(dir);
1088 }
1089
1090 // Verifies the files produced by an external unarchiver are as expected
1091 //
1092 template <class Classes>
1093 void ArchiveTestCase<Classes>::VerifyDir(wxString& path, size_t rootlen /*=0*/)
1094 {
1095 wxDir dir;
1096 path += wxFileName::GetPathSeparator();
1097 int pos = path.length();
1098 wxString name;
1099
1100 if (!rootlen)
1101 rootlen = pos;
1102
1103 if (dir.Open(path) && dir.GetFirst(&name)) {
1104 do {
1105 path.replace(pos, wxString::npos, name);
1106 name = m_factory->GetInternalName(
1107 path.substr(rootlen, wxString::npos));
1108
1109 bool isDir = wxDirExists(path);
1110 if (isDir)
1111 name += _T("/");
1112
1113 // provide some context for the error message so that we know which
1114 // iteration of the loop we were on
1115 string error_entry((_T(" '") + name + _T("'")).mb_str());
1116 string error_context(" failed for entry" + error_entry);
1117
1118 TestEntries::iterator it = m_testEntries.find(name);
1119 CPPUNIT_ASSERT_MESSAGE(
1120 "archive contains an entry that shouldn't be there"
1121 + error_entry,
1122 it != m_testEntries.end());
1123
1124 const TestEntry& testEntry = *it->second;
1125
1126 #ifndef __WXMSW__
1127 CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context,
1128 testEntry.GetDateTime() ==
1129 wxFileName(path).GetModificationTime());
1130 #endif
1131 if (!isDir) {
1132 wxFFileInputStream in(path);
1133 CPPUNIT_ASSERT_MESSAGE(
1134 "entry not found in archive" + error_entry, in.Ok());
1135
1136 size_t size = (size_t)in.GetLength();
1137 wxCharBuffer buf(size);
1138 CPPUNIT_ASSERT_MESSAGE("Read" + error_context,
1139 in.Read(buf.data(), size).LastRead() == size);
1140 CPPUNIT_ASSERT_MESSAGE("size check" + error_context,
1141 testEntry.GetSize() == size);
1142 CPPUNIT_ASSERT_MESSAGE("data compare" + error_context,
1143 memcmp(buf.data(), testEntry.GetData(), size) == 0);
1144 }
1145 else {
1146 VerifyDir(path, rootlen);
1147 }
1148
1149 delete it->second;
1150 m_testEntries.erase(it);
1151 }
1152 while (dir.GetNext(&name));
1153 }
1154 }
1155
1156 // test the simple iterators that give away ownership of an entry
1157 //
1158 template <class Classes>
1159 void ArchiveTestCase<Classes>::TestIterator(wxInputStream& in)
1160 {
1161 typedef std::list<EntryT*> ArchiveCatalog;
1162 typedef typename ArchiveCatalog::iterator CatalogIter;
1163
1164 auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1165 size_t count = 0;
1166
1167 #ifdef WXARC_MEMBER_TEMPLATES
1168 ArchiveCatalog cat((IterT)*arc, IterT());
1169 #else
1170 ArchiveCatalog cat;
1171 for (IterT i(*arc); i != IterT(); ++i)
1172 cat.push_back(*i);
1173 #endif
1174
1175 for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
1176 auto_ptr<EntryT> entry(*it);
1177 count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
1178 }
1179
1180 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1181 CPPUNIT_ASSERT(count == cat.size());
1182 }
1183
1184 // test the pair iterators that can be used to load a std::map or wxHashMap
1185 // these also give away ownership of entries
1186 //
1187 template <class Classes>
1188 void ArchiveTestCase<Classes>::TestPairIterator(wxInputStream& in)
1189 {
1190 typedef std::map<wxString, EntryT*> ArchiveCatalog;
1191 typedef typename ArchiveCatalog::iterator CatalogIter;
1192
1193 auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1194 size_t count = 0;
1195
1196 #ifdef WXARC_MEMBER_TEMPLATES
1197 ArchiveCatalog cat((PairIterT)*arc, PairIterT());
1198 #else
1199 ArchiveCatalog cat;
1200 for (PairIterT i(*arc); i != PairIterT(); ++i)
1201 cat.insert(*i);
1202 #endif
1203
1204 for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
1205 auto_ptr<EntryT> entry(it->second);
1206 count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
1207 }
1208
1209 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1210 CPPUNIT_ASSERT(count == cat.size());
1211 }
1212
1213 // simple iterators using smart pointers, no need to worry about ownership
1214 //
1215 template <class Classes>
1216 void ArchiveTestCase<Classes>::TestSmartIterator(wxInputStream& in)
1217 {
1218 typedef std::list<Ptr<EntryT> > ArchiveCatalog;
1219 typedef typename ArchiveCatalog::iterator CatalogIter;
1220 typedef wxArchiveIterator<InputStreamT, Ptr<EntryT> > Iter;
1221
1222 auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1223
1224 #ifdef WXARC_MEMBER_TEMPLATES
1225 ArchiveCatalog cat((Iter)*arc, Iter());
1226 #else
1227 ArchiveCatalog cat;
1228 for (Iter i(*arc); i != Iter(); ++i)
1229 cat.push_back(*i);
1230 #endif
1231
1232 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1233
1234 for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
1235 CPPUNIT_ASSERT(m_testEntries.count((*it)->GetName(wxPATH_UNIX)));
1236 }
1237
1238 // pair iterator using smart pointers
1239 //
1240 template <class Classes>
1241 void ArchiveTestCase<Classes>::TestSmartPairIterator(wxInputStream& in)
1242 {
1243 #if defined _MSC_VER && defined _MSC_VER < 1200
1244 // With VC++ 5.0 the '=' operator of std::pair breaks when the second
1245 // type is Ptr<EntryT>, so this iterator can't be made to work.
1246 (void)in;
1247 #else
1248 typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
1249 typedef typename ArchiveCatalog::iterator CatalogIter;
1250 typedef wxArchiveIterator<InputStreamT,
1251 std::pair<wxString, Ptr<EntryT> > > PairIter;
1252
1253 auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1254
1255 #ifdef WXARC_MEMBER_TEMPLATES
1256 ArchiveCatalog cat((PairIter)*arc, PairIter());
1257 #else
1258 ArchiveCatalog cat;
1259 for (PairIter i(*arc); i != PairIter(); ++i)
1260 cat.insert(*i);
1261 #endif
1262
1263 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1264
1265 for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
1266 CPPUNIT_ASSERT(m_testEntries.count(it->second->GetName(wxPATH_UNIX)));
1267 #endif
1268 }
1269
1270 // try reading two entries at the same time
1271 //
1272 template <class Classes>
1273 void ArchiveTestCase<Classes>::ReadSimultaneous(TestInputStream& in)
1274 {
1275 typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
1276 typedef wxArchiveIterator<InputStreamT,
1277 std::pair<wxString, Ptr<EntryT> > > PairIter;
1278
1279 // create two archive input streams
1280 TestInputStream in2(in);
1281 auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1282 auto_ptr<InputStreamT> arc2(m_factory->NewStream(in2));
1283
1284 // load the catalog
1285 #ifdef WXARC_MEMBER_TEMPLATES
1286 ArchiveCatalog cat((PairIter)*arc, PairIter());
1287 #else
1288 ArchiveCatalog cat;
1289 for (PairIter i(*arc); i != PairIter(); ++i)
1290 cat.insert(*i);
1291 #endif
1292
1293 // the names of two entries to read
1294 const wxChar *name = _T("text/small");
1295 const wxChar *name2 = _T("bin/bin1000");
1296
1297 // open them
1298 typename ArchiveCatalog::iterator j;
1299 CPPUNIT_ASSERT((j = cat.find(name)) != cat.end());
1300 CPPUNIT_ASSERT(arc->OpenEntry(*j->second));
1301 CPPUNIT_ASSERT((j = cat.find(name2)) != cat.end());
1302 CPPUNIT_ASSERT(arc2->OpenEntry(*j->second));
1303
1304 // get pointers to the expected data
1305 TestEntries::iterator k;
1306 CPPUNIT_ASSERT((k = m_testEntries.find(name)) != m_testEntries.end());
1307 TestEntry *entry = k->second;
1308 CPPUNIT_ASSERT((k = m_testEntries.find(name2)) != m_testEntries.end());
1309 TestEntry *entry2 = k->second;
1310
1311 size_t count = 0, count2 = 0;
1312 size_t size = entry->GetSize(), size2 = entry2->GetSize();
1313 const char *data = entry->GetData(), *data2 = entry2->GetData();
1314
1315 // read and check the two entries in parallel, character by character
1316 while (arc->IsOk() || arc2->IsOk()) {
1317 char ch = arc->GetC();
1318 if (arc->LastRead() == 1) {
1319 CPPUNIT_ASSERT(count < size);
1320 CPPUNIT_ASSERT(ch == data[count++]);
1321 }
1322 char ch2 = arc2->GetC();
1323 if (arc2->LastRead() == 1) {
1324 CPPUNIT_ASSERT(count2 < size2);
1325 CPPUNIT_ASSERT(ch2 == data2[count2++]);
1326 }
1327 }
1328
1329 CPPUNIT_ASSERT(arc->Eof());
1330 CPPUNIT_ASSERT(arc2->Eof());
1331 CPPUNIT_ASSERT(count == size);
1332 CPPUNIT_ASSERT(count2 == size2);
1333 }
1334
1335 // Nothing useful can be done with a generic notifier yet, so just test one
1336 // can be set
1337 //
1338 template <class NotifierT, class EntryT>
1339 class ArchiveNotifier : public NotifierT
1340 {
1341 public:
1342 void OnEntryUpdated(EntryT& WXUNUSED(entry)) { }
1343 };
1344
1345 template <class Classes>
1346 void ArchiveTestCase<Classes>::OnSetNotifier(EntryT& entry)
1347 {
1348 static ArchiveNotifier<NotifierT, EntryT> notifier;
1349 entry.SetNotifier(notifier);
1350 }
1351
1352
1353 ///////////////////////////////////////////////////////////////////////////////
1354 // ArchiveTestCase<ZipClasses> could be used directly, but instead this
1355 // derived class is used so that zip specific features can be tested.
1356
1357 class ZipTestCase : public ArchiveTestCase<ZipClasses>
1358 {
1359 public:
1360 ZipTestCase(string name,
1361 int id,
1362 int options,
1363 const wxString& archiver = wxEmptyString,
1364 const wxString& unarchiver = wxEmptyString)
1365 :
1366 ArchiveTestCase<ZipClasses>(name, id, new wxZipClassFactory,
1367 options, archiver, unarchiver),
1368 m_count(0)
1369 { }
1370
1371 protected:
1372 void OnCreateArchive(wxZipOutputStream& zip);
1373
1374 void OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal);
1375
1376 void OnCreateEntry(wxZipOutputStream& zip,
1377 TestEntry& testEntry,
1378 wxZipEntry *entry);
1379
1380 void OnEntryExtracted(wxZipEntry& entry,
1381 const TestEntry& testEntry,
1382 wxZipInputStream *arc);
1383
1384 void OnSetNotifier(EntryT& entry);
1385
1386 int m_count;
1387 wxString m_comment;
1388 };
1389
1390 void ZipTestCase::OnCreateArchive(wxZipOutputStream& zip)
1391 {
1392 m_comment << _T("Comment for test ") << m_id;
1393 zip.SetComment(m_comment);
1394 }
1395
1396 void ZipTestCase::OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal)
1397 {
1398 CPPUNIT_ASSERT(zip.GetComment() == m_comment);
1399 CPPUNIT_ASSERT(zip.GetTotalEntries() == expectedTotal);
1400 }
1401
1402 void ZipTestCase::OnCreateEntry(wxZipOutputStream& zip,
1403 TestEntry& testEntry,
1404 wxZipEntry *entry)
1405 {
1406 zip.SetLevel((m_id + m_count) % 10);
1407
1408 if (entry) {
1409 switch ((m_id + m_count) % 5) {
1410 case 0:
1411 {
1412 wxString comment = _T("Comment for ") + entry->GetName();
1413 entry->SetComment(comment);
1414 // lowercase the expected result, and the notifier should do
1415 // the same for the zip entries when ModifyArchive() runs
1416 testEntry.SetComment(comment.Lower());
1417 break;
1418 }
1419 case 2:
1420 entry->SetMethod(wxZIP_METHOD_STORE);
1421 break;
1422 case 4:
1423 entry->SetMethod(wxZIP_METHOD_DEFLATE);
1424 break;
1425 }
1426 entry->SetIsText(testEntry.IsText());
1427 }
1428
1429 m_count++;
1430 }
1431
1432 void ZipTestCase::OnEntryExtracted(wxZipEntry& entry,
1433 const TestEntry& testEntry,
1434 wxZipInputStream *arc)
1435 {
1436 // provide some context for the error message so that we know which
1437 // iteration of the loop we were on
1438 wxString name = _T(" '") + entry.GetName() + _T("'");
1439 string error_entry(name.mb_str());
1440 string error_context(" failed for entry" + error_entry);
1441
1442 CPPUNIT_ASSERT_MESSAGE("GetComment" + error_context,
1443 entry.GetComment() == testEntry.GetComment());
1444
1445 // for seekable streams, GetNextEntry() doesn't read the local header so
1446 // call OpenEntry() to do it
1447 if (arc && (m_options & PipeIn) == 0 && entry.IsDir())
1448 arc->OpenEntry(entry);
1449
1450 CPPUNIT_ASSERT_MESSAGE("IsText" + error_context,
1451 entry.IsText() == testEntry.IsText());
1452
1453 CPPUNIT_ASSERT_MESSAGE("Extra/LocalExtra mismatch for entry" + error_entry,
1454 (entry.GetExtraLen() != 0 && entry.GetLocalExtraLen() != 0) ||
1455 (entry.GetExtraLen() == 0 && entry.GetLocalExtraLen() == 0));
1456 }
1457
1458 // check the notifier mechanism by using it to fold the entry comments to
1459 // lowercase
1460 //
1461 class ZipNotifier : public wxZipNotifier
1462 {
1463 public:
1464 void OnEntryUpdated(wxZipEntry& entry);
1465 };
1466
1467 void ZipNotifier::OnEntryUpdated(wxZipEntry& entry)
1468 {
1469 entry.SetComment(entry.GetComment().Lower());
1470 }
1471
1472 void ZipTestCase::OnSetNotifier(EntryT& entry)
1473 {
1474 static ZipNotifier notifier;
1475 entry.SetNotifier(notifier);
1476 }
1477
1478
1479 ///////////////////////////////////////////////////////////////////////////////
1480 // 'zip - -' produces local headers without the size field set. This is a
1481 // case not covered by all the other tests, so this class tests it as a
1482 // special case
1483
1484 class ZipPipeTestCase : public CppUnit::TestCase
1485 {
1486 public:
1487 ZipPipeTestCase(string name, int options) :
1488 CppUnit::TestCase(name), m_options(options) { }
1489
1490 protected:
1491 void runTest();
1492 int m_options;
1493 };
1494
1495 void ZipPipeTestCase::runTest()
1496 {
1497 TestOutputStream out(m_options);
1498
1499 wxString testdata = _T("test data to pipe through zip");
1500 wxString cmd = _T("echo ") + testdata + _T(" | zip -q - -");
1501
1502 {
1503 PFileInputStream in(cmd);
1504 if (in.Ok())
1505 out.Write(in);
1506 }
1507
1508 TestInputStream in(out);
1509 wxZipInputStream zip(in);
1510
1511 auto_ptr<wxZipEntry> entry(zip.GetNextEntry());
1512 CPPUNIT_ASSERT(entry.get() != NULL);
1513
1514 if ((m_options & PipeIn) == 0)
1515 CPPUNIT_ASSERT(entry->GetSize() != wxInvalidOffset);
1516
1517 char buf[64];
1518 size_t len = zip.Read(buf, sizeof(buf) - 1).LastRead();
1519
1520 while (len > 0 && buf[len - 1] <= 32)
1521 --len;
1522 buf[len] = 0;
1523
1524 CPPUNIT_ASSERT(zip.Eof());
1525 CPPUNIT_ASSERT(wxString(buf, *wxConvCurrent) == testdata);
1526 }
1527
1528
1529 ///////////////////////////////////////////////////////////////////////////////
1530 // The suite
1531
1532 class ArchiveTestSuite : public CppUnit::TestSuite
1533 {
1534 public:
1535 ArchiveTestSuite();
1536 static CppUnit::Test *suite()
1537 { return (new ArchiveTestSuite)->makeSuite(); }
1538
1539 private:
1540 int m_id;
1541 wxPathList m_path;
1542
1543 ArchiveTestSuite *makeSuite();
1544 void AddCmd(wxArrayString& cmdlist, const wxString& cmd);
1545 bool IsInPath(const wxString& cmd);
1546
1547 string Description(const wxString& type,
1548 int options,
1549 bool genericInterface = false,
1550 const wxString& archiver = wxEmptyString,
1551 const wxString& unarchiver = wxEmptyString);
1552 };
1553
1554 ArchiveTestSuite::ArchiveTestSuite()
1555 : CppUnit::TestSuite("ArchiveTestSuite"),
1556 m_id(0)
1557 {
1558 m_path.AddEnvList(_T("PATH"));
1559 }
1560
1561 // add the command for an external archiver to the list, testing for it in
1562 // the path first
1563 //
1564 void ArchiveTestSuite::AddCmd(wxArrayString& cmdlist, const wxString& cmd)
1565 {
1566 if (cmdlist.empty())
1567 cmdlist.push_back(_T(""));
1568 if (IsInPath(cmd))
1569 cmdlist.push_back(cmd);
1570 }
1571
1572 bool ArchiveTestSuite::IsInPath(const wxString& cmd)
1573 {
1574 wxString c = cmd.BeforeFirst(_T(' '));
1575 #ifdef __WXMSW__
1576 c += _T(".exe");
1577 #endif
1578 return !m_path.FindValidPath(c).empty();
1579 }
1580
1581 // make the test suite
1582 //
1583 ArchiveTestSuite *ArchiveTestSuite::makeSuite()
1584 {
1585 typedef wxArrayString::iterator Iter;
1586 wxArrayString zippers;
1587 wxArrayString unzippers;
1588
1589 AddCmd(zippers, _T("zip -qr %s *"));
1590 AddCmd(unzippers, _T("unzip -q %s"));
1591
1592 for (int genInterface = 0; genInterface < 2; genInterface++)
1593 for (Iter i = unzippers.begin(); i != unzippers.end(); ++i)
1594 for (Iter j = zippers.begin(); j != zippers.end(); ++j)
1595 for (int options = 0; options <= AllOptions; options++)
1596 {
1597 // unzip doesn't support piping in the zip
1598 if ((options & PipeIn) && !i->empty())
1599 continue;
1600 #ifdef WXARC_NO_POPEN
1601 // if no popen then can use piped output of zip
1602 if ((options & PipeOut) && !j->empty())
1603 continue;
1604 #endif
1605 string name = Description(_T("wxZip"), options,
1606 genInterface != 0, *j, *i);
1607
1608 if (genInterface)
1609 addTest(new ArchiveTestCase<ArchiveClasses>(
1610 name, m_id,
1611 new wxZipClassFactory,
1612 options, *j, *i));
1613 else
1614 addTest(new ZipTestCase(name, m_id, options, *j, *i));
1615
1616 m_id++;
1617 }
1618
1619 #ifndef WXARC_NO_POPEN
1620 // if have popen then can check the piped output of 'zip - -'
1621 if (IsInPath(_T("zip")))
1622 for (int options = 0; options <= PipeIn; options += PipeIn) {
1623 string name = Description(_T("ZipPipeTestCase"), options);
1624 addTest(new ZipPipeTestCase(name, options));
1625 m_id++;
1626 }
1627 #endif
1628
1629 return this;
1630 }
1631
1632 // make a display string for the option bits
1633 //
1634 string ArchiveTestSuite::Description(const wxString& type,
1635 int options,
1636 bool genericInterface,
1637 const wxString& archiver,
1638 const wxString& unarchiver)
1639 {
1640 wxString descr;
1641 descr << m_id << _T(" ");
1642
1643 if (genericInterface)
1644 descr << _T("wxArchive (") << type << _T(")");
1645 else
1646 descr << type;
1647
1648 if (!archiver.empty())
1649 descr << _T(" ") << archiver.BeforeFirst(_T(' '));
1650 if (!unarchiver.empty())
1651 descr << _T(" ") << unarchiver.BeforeFirst(_T(' '));
1652
1653 wxString optstr;
1654
1655 if ((options & PipeIn) != 0)
1656 optstr += _T("|PipeIn");
1657 if ((options & PipeOut) != 0)
1658 optstr += _T("|PipeOut");
1659 if ((options & Stub) != 0)
1660 optstr += _T("|Stub");
1661 if (!optstr.empty())
1662 optstr = _T(" (") + optstr.substr(1) + _T(")");
1663
1664 descr << optstr;
1665
1666 return (const char*)descr.mb_str();
1667 }
1668
1669 // register in the unnamed registry so that these tests are run by default
1670 CPPUNIT_TEST_SUITE_REGISTRATION(ArchiveTestSuite);
1671
1672 // also include in it's own registry so that these tests can be run alone
1673 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ArchiveTestSuite, "ArchiveTestSuite");
1674
1675 #endif // wxUSE_STREAMS