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