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