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