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