]> git.saurik.com Git - wxWidgets.git/blob - tests/archive/archivetest.cpp
e836c3a16eb433e4e7d1c3dc539b63c835e594d4
[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 size_t GetSize() 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 ? 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 size_t GetSize() 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 ? 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.GetSize() > 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->GetSize() == entry->GetSize()" + error_context,
979 arc->GetSize() == (size_t)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->GetSize() == entry->GetSize()" + error_context,
1002 arc->GetSize() == (size_t)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 size_t size = 0;
1106
1107 #ifndef __WXMSW__
1108 CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context,
1109 testEntry.GetDateTime() ==
1110 wxFileName(path).GetModificationTime());
1111 #endif
1112 if (!isDir) {
1113 wxFFileInputStream in(path);
1114 CPPUNIT_ASSERT_MESSAGE(
1115 "entry not found in archive" + error_entry, in.Ok());
1116
1117 size = in.GetSize();
1118 wxCharBuffer buf(size);
1119 CPPUNIT_ASSERT_MESSAGE("Read" + error_context,
1120 in.Read(buf.data(), size).LastRead() == size);
1121 CPPUNIT_ASSERT_MESSAGE("size check" + error_context,
1122 testEntry.GetSize() == size);
1123 CPPUNIT_ASSERT_MESSAGE("data compare" + error_context,
1124 memcmp(buf.data(), testEntry.GetData(), size) == 0);
1125 }
1126 else {
1127 VerifyDir(path, rootlen);
1128 }
1129
1130 delete it->second;
1131 m_testEntries.erase(it);
1132 }
1133 while (dir.GetNext(&name));
1134 }
1135 }
1136
1137 // test the simple iterators that give away ownership of an entry
1138 //
1139 template <class Classes>
1140 void ArchiveTestCase<Classes>::TestIterator(wxInputStream& in)
1141 {
1142 typedef std::list<EntryT*> ArchiveCatalog;
1143 typedef typename ArchiveCatalog::iterator CatalogIter;
1144
1145 std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1146 size_t count = 0;
1147
1148 #ifdef WXARC_MEMBER_TEMPLATES
1149 ArchiveCatalog cat((IterT)*arc, IterT());
1150 #else
1151 ArchiveCatalog cat;
1152 for (IterT i(*arc); i != IterT(); ++i)
1153 cat.push_back(*i);
1154 #endif
1155
1156 for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
1157 std::auto_ptr<EntryT> entry(*it);
1158 count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
1159 }
1160
1161 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1162 CPPUNIT_ASSERT(count == cat.size());
1163 }
1164
1165 // test the pair iterators that can be used to load a std::map or wxHashMap
1166 // these also give away ownership of entries
1167 //
1168 template <class Classes>
1169 void ArchiveTestCase<Classes>::TestPairIterator(wxInputStream& in)
1170 {
1171 typedef std::map<wxString, EntryT*> ArchiveCatalog;
1172 typedef typename ArchiveCatalog::iterator CatalogIter;
1173
1174 std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1175 size_t count = 0;
1176
1177 #ifdef WXARC_MEMBER_TEMPLATES
1178 ArchiveCatalog cat((PairIterT)*arc, PairIterT());
1179 #else
1180 ArchiveCatalog cat;
1181 for (PairIterT i(*arc); i != PairIterT(); ++i)
1182 cat.push_back(*i);
1183 #endif
1184
1185 for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
1186 std::auto_ptr<EntryT> entry(it->second);
1187 count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
1188 }
1189
1190 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1191 CPPUNIT_ASSERT(count == cat.size());
1192 }
1193
1194 // simple iterators using smart pointers, no need to worry about ownership
1195 //
1196 template <class Classes>
1197 void ArchiveTestCase<Classes>::TestSmartIterator(wxInputStream& in)
1198 {
1199 typedef std::list<Ptr<EntryT> > ArchiveCatalog;
1200 typedef typename ArchiveCatalog::iterator CatalogIter;
1201 typedef wxArchiveIterator<InputStreamT, Ptr<EntryT> > Iter;
1202
1203 std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1204
1205 #ifdef WXARC_MEMBER_TEMPLATES
1206 ArchiveCatalog cat((Iter)*arc, Iter());
1207 #else
1208 ArchiveCatalog cat;
1209 for (Iter i(*arc); i != Iter(); ++i)
1210 cat.push_back(*i);
1211 #endif
1212
1213 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1214
1215 for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
1216 CPPUNIT_ASSERT(m_testEntries.count((*it)->GetName(wxPATH_UNIX)));
1217 }
1218
1219 // pair iterator using smart pointers
1220 //
1221 template <class Classes>
1222 void ArchiveTestCase<Classes>::TestSmartPairIterator(wxInputStream& in)
1223 {
1224 typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
1225 typedef typename ArchiveCatalog::iterator CatalogIter;
1226 typedef wxArchiveIterator<InputStreamT,
1227 std::pair<wxString, Ptr<EntryT> > > PairIter;
1228
1229 std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1230
1231 #ifdef WXARC_MEMBER_TEMPLATES
1232 ArchiveCatalog cat((PairIter)*arc, PairIter());
1233 #else
1234 ArchiveCatalog cat;
1235 for (PairIter i(*arc); i != PairIter(); ++i)
1236 cat.push_back(*i);
1237 #endif
1238
1239 CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
1240
1241 for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
1242 CPPUNIT_ASSERT(m_testEntries.count(it->second->GetName(wxPATH_UNIX)));
1243 }
1244
1245 // try reading two entries at the same time
1246 //
1247 template <class Classes>
1248 void ArchiveTestCase<Classes>::ReadSimultaneous(TestInputStream& in)
1249 {
1250 typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
1251 typedef wxArchiveIterator<InputStreamT,
1252 std::pair<wxString, Ptr<EntryT> > > PairIter;
1253
1254 // create two archive input streams
1255 TestInputStream in2(in);
1256 std::auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
1257 std::auto_ptr<InputStreamT> arc2(m_factory->NewStream(in2));
1258
1259 // load the catalog
1260 #ifdef WXARC_MEMBER_TEMPLATES
1261 ArchiveCatalog cat((PairIter)*arc, PairIter());
1262 #else
1263 ArchiveCatalog cat;
1264 for (PairIter i(*arc); i != PairIter(); ++i)
1265 cat.push_back(*i);
1266 #endif
1267
1268 // the names of two entries to read
1269 const wxChar *name = _T("text/small");
1270 const wxChar *name2 = _T("bin/bin1000");
1271
1272 // open them
1273 typename ArchiveCatalog::iterator j;
1274 CPPUNIT_ASSERT((j = cat.find(name)) != cat.end());
1275 CPPUNIT_ASSERT(arc->OpenEntry(*j->second));
1276 CPPUNIT_ASSERT((j = cat.find(name2)) != cat.end());
1277 CPPUNIT_ASSERT(arc2->OpenEntry(*j->second));
1278
1279 // get pointers to the expected data
1280 TestEntries::iterator k;
1281 CPPUNIT_ASSERT((k = m_testEntries.find(name)) != m_testEntries.end());
1282 TestEntry *entry = k->second;
1283 CPPUNIT_ASSERT((k = m_testEntries.find(name2)) != m_testEntries.end());
1284 TestEntry *entry2 = k->second;
1285
1286 size_t count = 0, count2 = 0;
1287 size_t size = entry->GetSize(), size2 = entry2->GetSize();
1288 const char *data = entry->GetData(), *data2 = entry2->GetData();
1289
1290 // read and check the two entries in parallel, character by character
1291 while (arc->IsOk() || arc2->IsOk()) {
1292 char ch = arc->GetC();
1293 if (arc->LastRead() == 1) {
1294 CPPUNIT_ASSERT(count < size);
1295 CPPUNIT_ASSERT(ch == data[count++]);
1296 }
1297 char ch2 = arc2->GetC();
1298 if (arc2->LastRead() == 1) {
1299 CPPUNIT_ASSERT(count2 < size2);
1300 CPPUNIT_ASSERT(ch2 == data2[count2++]);
1301 }
1302 }
1303
1304 CPPUNIT_ASSERT(arc->Eof());
1305 CPPUNIT_ASSERT(arc2->Eof());
1306 CPPUNIT_ASSERT(count == size);
1307 CPPUNIT_ASSERT(count2 == size2);
1308 }
1309
1310 // Nothing useful can be done with a generic notifier yet, so just test one
1311 // can be set
1312 //
1313 template <class NotifierT, class EntryT>
1314 class ArchiveNotifier : public NotifierT
1315 {
1316 public:
1317 void OnEntryUpdated(EntryT& WXUNUSED(entry)) { }
1318 };
1319
1320 template <class Classes>
1321 void ArchiveTestCase<Classes>::OnSetNotifier(EntryT& entry)
1322 {
1323 static ArchiveNotifier<NotifierT, EntryT> notifier;
1324 entry.SetNotifier(notifier);
1325 }
1326
1327
1328 ///////////////////////////////////////////////////////////////////////////////
1329 // ArchiveTestCase<ZipClasses> could be used directly, but instead this
1330 // derived class is used so that zip specific features can be tested.
1331
1332 class ZipTestCase : public ArchiveTestCase<ZipClasses>
1333 {
1334 public:
1335 ZipTestCase(const wxString& name,
1336 int id,
1337 int options,
1338 const wxString& archiver = wxEmptyString,
1339 const wxString& unarchiver = wxEmptyString)
1340 :
1341 ArchiveTestCase<ZipClasses>(name, id, new wxZipClassFactory,
1342 options, archiver, unarchiver),
1343 m_count(0)
1344 { }
1345
1346 protected:
1347 void OnCreateArchive(wxZipOutputStream& zip);
1348
1349 void OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal);
1350
1351 void OnCreateEntry(wxZipOutputStream& zip,
1352 TestEntry& testEntry,
1353 wxZipEntry *entry);
1354
1355 void OnEntryExtracted(wxZipEntry& entry,
1356 const TestEntry& testEntry,
1357 wxZipInputStream *arc);
1358
1359 void OnSetNotifier(EntryT& entry);
1360
1361 int m_count;
1362 wxString m_comment;
1363 };
1364
1365 void ZipTestCase::OnCreateArchive(wxZipOutputStream& zip)
1366 {
1367 m_comment << _T("Comment for test ") << m_id;
1368 zip.SetComment(m_comment);
1369 }
1370
1371 void ZipTestCase::OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal)
1372 {
1373 CPPUNIT_ASSERT(zip.GetComment() == m_comment);
1374 CPPUNIT_ASSERT(zip.GetTotalEntries() == expectedTotal);
1375 }
1376
1377 void ZipTestCase::OnCreateEntry(wxZipOutputStream& zip,
1378 TestEntry& testEntry,
1379 wxZipEntry *entry)
1380 {
1381 zip.SetLevel((m_id + m_count) % 10);
1382
1383 if (entry) {
1384 switch ((m_id + m_count) % 5) {
1385 case 0:
1386 {
1387 wxString comment = _T("Comment for ") + entry->GetName();
1388 entry->SetComment(comment);
1389 // lowercase the expected result, and the notifier should do
1390 // the same for the zip entries when ModifyArchive() runs
1391 testEntry.SetComment(comment.Lower());
1392 break;
1393 }
1394 case 2:
1395 entry->SetMethod(wxZIP_METHOD_STORE);
1396 break;
1397 case 4:
1398 entry->SetMethod(wxZIP_METHOD_DEFLATE);
1399 break;
1400 }
1401 entry->SetIsText(testEntry.IsText());
1402 }
1403
1404 m_count++;
1405 }
1406
1407 void ZipTestCase::OnEntryExtracted(wxZipEntry& entry,
1408 const TestEntry& testEntry,
1409 wxZipInputStream *arc)
1410 {
1411 // provide some context for the error message so that we know which
1412 // iteration of the loop we were on
1413 std::string error_entry((_T(" '") + entry.GetName() + _T("'")).mb_str());
1414 std::string error_context(" failed for entry" + error_entry);
1415
1416 CPPUNIT_ASSERT_MESSAGE("GetComment" + error_context,
1417 entry.GetComment() == testEntry.GetComment());
1418
1419 // for seekable streams, GetNextEntry() doesn't read the local header so
1420 // call OpenEntry() to do it
1421 if (arc && (m_options & PipeIn) == 0 && entry.IsDir())
1422 arc->OpenEntry(entry);
1423
1424 CPPUNIT_ASSERT_MESSAGE("IsText" + error_context,
1425 entry.IsText() == testEntry.IsText());
1426
1427 CPPUNIT_ASSERT_MESSAGE("Extra/LocalExtra mismatch for entry" + error_entry,
1428 (entry.GetExtraLen() != 0 && entry.GetLocalExtraLen() != 0) ||
1429 (entry.GetExtraLen() == 0 && entry.GetLocalExtraLen() == 0));
1430 }
1431
1432 // check the notifier mechanism by using it to fold the entry comments to
1433 // lowercase
1434 //
1435 class ZipNotifier : public wxZipNotifier
1436 {
1437 public:
1438 void OnEntryUpdated(wxZipEntry& entry);
1439 };
1440
1441 void ZipNotifier::OnEntryUpdated(wxZipEntry& entry)
1442 {
1443 entry.SetComment(entry.GetComment().Lower());
1444 }
1445
1446 void ZipTestCase::OnSetNotifier(EntryT& entry)
1447 {
1448 static ZipNotifier notifier;
1449 entry.SetNotifier(notifier);
1450 }
1451
1452
1453 ///////////////////////////////////////////////////////////////////////////////
1454 // 'zip - -' produces local headers without the size field set. This is a
1455 // case not covered by all the other tests, so this class tests it as a
1456 // special case
1457
1458 class ZipPipeTestCase : public CppUnit::TestCase
1459 {
1460 public:
1461 ZipPipeTestCase(const wxString& name, int options) :
1462 CppUnit::TestCase(std::string(name.mb_str())), m_options(options) { }
1463
1464 protected:
1465 void runTest();
1466 int m_options;
1467 };
1468
1469 void ZipPipeTestCase::runTest()
1470 {
1471 TestOutputStream out(m_options);
1472
1473 wxString testdata = _T("test data to pipe through zip");
1474 wxString cmd = _T("echo ") + testdata + _T(" | zip -q - -");
1475
1476 {
1477 PFileInputStream in(cmd);
1478 if (in.Ok())
1479 out.Write(in);
1480 }
1481
1482 TestInputStream in(out);
1483 wxZipInputStream zip(in);
1484
1485 std::auto_ptr<wxZipEntry> entry(zip.GetNextEntry());
1486 CPPUNIT_ASSERT(entry.get() != NULL);
1487
1488 if ((m_options & PipeIn) == 0)
1489 CPPUNIT_ASSERT(entry->GetSize() != wxInvalidOffset);
1490
1491 char buf[64];
1492 size_t len = zip.Read(buf, sizeof(buf) - 1).LastRead();
1493
1494 while (len > 0 && buf[len - 1] <= 32)
1495 --len;
1496 buf[len] = 0;
1497
1498 CPPUNIT_ASSERT(zip.Eof());
1499 CPPUNIT_ASSERT(wxString(buf, *wxConvCurrent) == testdata);
1500 }
1501
1502
1503 ///////////////////////////////////////////////////////////////////////////////
1504 // The suite
1505
1506 class ArchiveTestSuite : public CppUnit::TestSuite
1507 {
1508 public:
1509 ArchiveTestSuite();
1510 static CppUnit::Test *suite()
1511 { return (new ArchiveTestSuite)->makeSuite(); }
1512
1513 private:
1514 int m_id;
1515 wxPathList m_path;
1516
1517 ArchiveTestSuite *makeSuite();
1518 void AddCmd(wxArrayString& cmdlist, const wxString& cmd);
1519 bool IsInPath(const wxString& cmd);
1520
1521 wxString Description(const wxString& type,
1522 int options,
1523 bool genericInterface = false,
1524 const wxString& archiver = wxEmptyString,
1525 const wxString& unarchiver = wxEmptyString);
1526 };
1527
1528 ArchiveTestSuite::ArchiveTestSuite()
1529 : CppUnit::TestSuite("ArchiveTestSuite"),
1530 m_id(0)
1531 {
1532 m_path.AddEnvList(_T("PATH"));
1533 }
1534
1535 // add the command for an external archiver to the list, testing for it in
1536 // the path first
1537 //
1538 void ArchiveTestSuite::AddCmd(wxArrayString& cmdlist, const wxString& cmd)
1539 {
1540 if (cmdlist.empty())
1541 cmdlist.push_back(_T(""));
1542 if (IsInPath(cmd))
1543 cmdlist.push_back(cmd);
1544 }
1545
1546 bool ArchiveTestSuite::IsInPath(const wxString& cmd)
1547 {
1548 wxString c = cmd.BeforeFirst(_T(' '));
1549 #ifdef __WXMSW__
1550 c += _T(".exe");
1551 #endif
1552 return !m_path.FindValidPath(c).empty();
1553 }
1554
1555 // make the test suite
1556 //
1557 ArchiveTestSuite *ArchiveTestSuite::makeSuite()
1558 {
1559 typedef wxArrayString::iterator Iter;
1560 wxArrayString zippers;
1561 wxArrayString unzippers;
1562
1563 AddCmd(zippers, _T("zip -qr %s *"));
1564 AddCmd(unzippers, _T("unzip -q %s"));
1565
1566 for (int genInterface = 0; genInterface < 2; genInterface++)
1567 for (Iter i = unzippers.begin(); i != unzippers.end(); ++i)
1568 for (Iter j = zippers.begin(); j != zippers.end(); ++j)
1569 for (int options = 0; options <= AllOptions; options++)
1570 {
1571 // unzip doesn't support piping in the zip
1572 if ((options & PipeIn) && !i->empty())
1573 continue;
1574 #ifdef WXARC_NO_POPEN
1575 // if no popen then can use piped output of zip
1576 if ((options & PipeOut) && !j->empty())
1577 continue;
1578 #endif
1579 wxString name = Description(_T("wxZip"), options,
1580 genInterface != 0, *j, *i);
1581
1582 if (genInterface)
1583 addTest(new ArchiveTestCase<ArchiveClasses>(
1584 name, m_id,
1585 new wxZipClassFactory,
1586 options, *j, *i));
1587 else
1588 addTest(new ZipTestCase(name, m_id, options, *j, *i));
1589
1590 m_id++;
1591 }
1592
1593 #ifndef WXARC_NO_POPEN
1594 // if have popen then can check the piped output of 'zip - -'
1595 if (IsInPath(_T("zip")))
1596 for (int options = 0; options <= PipeIn; options += PipeIn) {
1597 wxString name = Description(_T("ZipPipeTestCase"), options);
1598 addTest(new ZipPipeTestCase(name, options));
1599 m_id++;
1600 }
1601 #endif
1602
1603 return this;
1604 }
1605
1606 // make a display string for the option bits
1607 //
1608 wxString ArchiveTestSuite::Description(const wxString& type,
1609 int options,
1610 bool genericInterface,
1611 const wxString& archiver,
1612 const wxString& unarchiver)
1613 {
1614 wxString descr;
1615 descr << m_id << _T(" ");
1616
1617 if (genericInterface)
1618 descr << _T("wxArchive (") << type << _T(")");
1619 else
1620 descr << type;
1621
1622 if (!archiver.empty())
1623 descr << _T(" ") << archiver.BeforeFirst(_T(' '));
1624 if (!unarchiver.empty())
1625 descr << _T(" ") << unarchiver.BeforeFirst(_T(' '));
1626
1627 wxString optstr;
1628
1629 if ((options & PipeIn) != 0)
1630 optstr += _T("|PipeIn");
1631 if ((options & PipeOut) != 0)
1632 optstr += _T("|PipeOut");
1633 if ((options & Stub) != 0)
1634 optstr += _T("|Stub");
1635 if (!optstr.empty())
1636 optstr = _T(" (") + optstr.substr(1) + _T(")");
1637
1638 descr << optstr;
1639
1640 return descr;
1641 }
1642
1643 // register in the unnamed registry so that these tests are run by default
1644 CPPUNIT_TEST_SUITE_REGISTRATION(ArchiveTestSuite);
1645
1646 // also include in it's own registry so that these tests can be run alone
1647 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ArchiveTestSuite, "ArchiveTestSuite");
1648
1649 #endif // wxUSE_STREAMS