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