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