wxWidgets/tests/archive/archivetest.cpp

1410 lines
41 KiB
C++
Raw Normal View History

///////////////////////////////////////////////////////////////////////////////
// Name: tests/archive/archive.cpp
// Purpose: Test the archive classes
// Author: Mike Wetherell
// RCS-ID: $Id$
// Copyright: (c) 2004 Mike Wetherell
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#include "testprec.h"
#ifdef __BORLANDC__
# pragma hdrstop
#endif
#ifndef WX_PRECOMP
# include "wx/wx.h"
#endif
#if wxUSE_STREAMS && wxUSE_ARCHIVE_STREAMS
// VC++ 6 warns that the list iterator's '->' operator will not work whenever
// std::list is used with a non-pointer, so switch it off.
#if defined _MSC_VER && _MSC_VER < 1300
#pragma warning (disable:4284)
#endif
#include "archivetest.h"
#include "wx/dir.h"
#include <string>
#include <list>
#include <map>
#include <sys/stat.h>
using std::string;
using std::auto_ptr;
// Check whether member templates can be used
//
#if defined __GNUC__ && \
(__GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95))
# define WXARC_MEMBER_TEMPLATES
#endif
#if defined _MSC_VER && _MSC_VER >= 1310 && !defined __WIN64__
# define WXARC_MEMBER_TEMPLATES
#endif
#if defined __BORLANDC__ && __BORLANDC__ >= 0x530
# define WXARC_MEMBER_TEMPLATES
#endif
#if defined __DMC__ && __DMC__ >= 0x832
# define WXARC_MEMBER_TEMPLATES
#endif
#if defined __MWERKS__ && __MWERKS__ >= 0x2200
# define WXARC_MEMBER_TEMPLATES
#endif
#if defined __HP_aCC && __HP_aCC > 33300
# define WXARC_MEMBER_TEMPLATES
#endif
#if defined __SUNPRO_CC && __SUNPRO_CC > 0x500
# define WXARC_MEMBER_TEMPLATES
#endif
///////////////////////////////////////////////////////////////////////////////
// A class to hold a test entry
TestEntry::TestEntry(const wxDateTime& dt, int len, const char *data)
: m_dt(dt),
m_len(len),
m_isText(len > 0)
{
m_data = new char[len];
memcpy(m_data, data, len);
for (int i = 0; i < len && m_isText; i++)
m_isText = (signed char)m_data[i] > 0;
}
///////////////////////////////////////////////////////////////////////////////
// TestOutputStream and TestInputStream are memory streams which can be
// seekable or non-seekable.
const size_t STUB_SIZE = 2048;
const size_t INITIAL_SIZE = 0x18000;
const wxFileOffset SEEK_LIMIT = 0x100000;
TestOutputStream::TestOutputStream(int options)
: m_options(options)
{
Init();
}
void TestOutputStream::Init()
{
m_data = NULL;
m_size = 0;
m_capacity = 0;
m_pos = 0;
if (m_options & Stub) {
wxCharBuffer buf(STUB_SIZE);
memset(buf.data(), 0, STUB_SIZE);
Write(buf, STUB_SIZE);
}
}
wxFileOffset TestOutputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
{
if ((m_options & PipeOut) == 0) {
switch (mode) {
case wxFromStart: break;
case wxFromCurrent: pos += m_pos; break;
case wxFromEnd: pos += m_size; break;
}
if (pos < 0 || pos > SEEK_LIMIT)
return wxInvalidOffset;
m_pos = (size_t)pos;
return m_pos;
}
return wxInvalidOffset;
}
wxFileOffset TestOutputStream::OnSysTell() const
{
return (m_options & PipeOut) == 0 ? (wxFileOffset)m_pos : wxInvalidOffset;
}
size_t TestOutputStream::OnSysWrite(const void *buffer, size_t size)
{
if (!IsOk() || !size)
return 0;
m_lasterror = wxSTREAM_WRITE_ERROR;
size_t newsize = m_pos + size;
wxCHECK(newsize > m_pos, 0);
if (m_capacity < newsize) {
size_t capacity = m_capacity ? m_capacity : INITIAL_SIZE;
while (capacity < newsize) {
capacity <<= 1;
wxCHECK(capacity > m_capacity, 0);
}
char *buf = new char[capacity];
if (m_data)
memcpy(buf, m_data, m_capacity);
delete [] m_data;
m_data = buf;
m_capacity = capacity;
}
memcpy(m_data + m_pos, buffer, size);
m_pos += size;
if (m_pos > m_size)
m_size = m_pos;
m_lasterror = wxSTREAM_NO_ERROR;
return size;
}
void TestOutputStream::GetData(char*& data, size_t& size)
{
data = m_data;
size = m_size;
if (m_options & Stub) {
char *d = m_data;
size += STUB_SIZE;
if (size > m_capacity) {
d = new char[size];
memcpy(d + STUB_SIZE, m_data, m_size);
delete [] m_data;
}
else {
memmove(d + STUB_SIZE, d, m_size);
}
memset(d, 0, STUB_SIZE);
data = d;
}
Init();
Reset();
}
///////////////////////////////////////////////////////////////////////////////
// TestOutputStream and TestInputStream are memory streams which can be
// seekable or non-seekable.
TestInputStream::TestInputStream(const TestInputStream& in)
: wxInputStream(),
m_options(in.m_options),
m_pos(in.m_pos),
m_size(in.m_size),
m_eoftype(in.m_eoftype)
{
m_data = new char[m_size];
memcpy(m_data, in.m_data, m_size);
}
void TestInputStream::Rewind()
{
if ((m_options & Stub) && (m_options & PipeIn))
m_pos = STUB_SIZE * 2;
else
m_pos = 0;
if (m_wbacksize) {
free(m_wback);
m_wback = NULL;
m_wbacksize = 0;
m_wbackcur = 0;
}
Reset();
}
void TestInputStream::SetData(TestOutputStream& out)
{
delete [] m_data;
m_options = out.GetOptions();
out.GetData(m_data, m_size);
Rewind();
}
wxFileOffset TestInputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
{
if ((m_options & PipeIn) == 0) {
switch (mode) {
case wxFromStart: break;
case wxFromCurrent: pos += m_pos; break;
case wxFromEnd: pos += m_size; break;
}
if (pos < 0 || pos > SEEK_LIMIT)
return wxInvalidOffset;
m_pos = (size_t)pos;
return m_pos;
}
return wxInvalidOffset;
}
wxFileOffset TestInputStream::OnSysTell() const
{
return (m_options & PipeIn) == 0 ? (wxFileOffset)m_pos : wxInvalidOffset;
}
size_t TestInputStream::OnSysRead(void *buffer, size_t size)
{
if (!IsOk() || !size)
return 0;
size_t count;
if (m_pos >= m_size)
count = 0;
else if (m_size - m_pos < size)
count = m_size - m_pos;
else
count = size;
if (count) {
memcpy(buffer, m_data + m_pos, count);
m_pos += count;
}
if (((m_eoftype & AtLast) != 0 && m_pos >= m_size) || count < size)
if ((m_eoftype & WithError) != 0)
m_lasterror = wxSTREAM_READ_ERROR;
else
m_lasterror = wxSTREAM_EOF;
return count;
}
///////////////////////////////////////////////////////////////////////////////
// minimal non-intrusive reference counting pointer for testing the iterators
template <class T> class Ptr
{
public:
explicit Ptr(T* p = NULL) : m_p(p), m_count(new int) { *m_count = 1; }
Ptr(const Ptr& sp) : m_p(sp.m_p), m_count(sp.m_count) { ++*m_count; }
~Ptr() { Free(); }
Ptr& operator =(const Ptr& sp) {
if (&sp != this) {
Free();
m_p = sp.m_p;
m_count = sp.m_count;
++*m_count;
}
return *this;
}
T* get() const { return m_p; }
T* operator->() const { return m_p; }
T& operator*() const { return *m_p; }
private:
void Free() {
if (--*m_count == 0) {
delete m_p;
delete m_count;
}
}
T *m_p;
int *m_count;
};
///////////////////////////////////////////////////////////////////////////////
// Clean-up for temp directory
class TempDir
{
public:
TempDir();
~TempDir();
wxString GetName() const { return m_tmp; }
private:
void RemoveDir(wxString& path);
wxString m_tmp;
wxString m_original;
};
TempDir::TempDir()
{
wxString tmp = wxFileName::CreateTempFileName(_T("arctest-"));
if (!tmp.empty()) {
wxRemoveFile(tmp);
m_original = wxGetCwd();
CPPUNIT_ASSERT(wxMkdir(tmp, 0700));
m_tmp = tmp;
CPPUNIT_ASSERT(wxSetWorkingDirectory(tmp));
}
}
TempDir::~TempDir()
{
if (!m_tmp.empty()) {
wxSetWorkingDirectory(m_original);
RemoveDir(m_tmp);
}
}
void TempDir::RemoveDir(wxString& path)
{
wxCHECK_RET(!m_tmp.empty() && path.substr(0, m_tmp.length()) == m_tmp,
_T("remove '") + path + _T("' fails safety check"));
const wxChar *files[] = {
_T("text/empty"),
_T("text/small"),
_T("bin/bin1000"),
_T("bin/bin4095"),
_T("bin/bin4096"),
_T("bin/bin4097"),
_T("bin/bin16384"),
_T("zero/zero5"),
_T("zero/zero1024"),
_T("zero/zero32768"),
_T("zero/zero16385"),
_T("zero/newname"),
_T("newfile"),
};
const wxChar *dirs[] = {
_T("text/"), _T("bin/"), _T("zero/"), _T("empty/")
};
wxString tmp = m_tmp + wxFileName::GetPathSeparator();
size_t i;
for (i = 0; i < WXSIZEOF(files); i++)
wxRemoveFile(tmp + wxFileName(files[i], wxPATH_UNIX).GetFullPath());
for (i = 0; i < WXSIZEOF(dirs); i++)
wxRmdir(tmp + wxFileName(dirs[i], wxPATH_UNIX).GetFullPath());
if (!wxRmdir(m_tmp))
wxLogSysError(_T("can't remove temporary dir '%s'"), m_tmp.c_str());
}
///////////////////////////////////////////////////////////////////////////////
// wxFFile streams for piping to/from an external program
#if defined __UNIX__ || defined __MINGW32__
# define WXARC_popen popen
# define WXARC_pclose pclose
#elif defined _MSC_VER || defined __BORLANDC__
# define WXARC_popen _popen
# define WXARC_pclose _pclose
#else
# define WXARC_NO_POPEN
# define WXARC_popen(cmd, type) NULL
# define WXARC_pclose(fp)
#endif
#ifdef __WXMSW__
# define WXARC_b "b"
#else
# define WXARC_b
#endif
PFileInputStream::PFileInputStream(const wxString& cmd)
: wxFFileInputStream(WXARC_popen(cmd.mb_str(), "r" WXARC_b))
{
}
PFileInputStream::~PFileInputStream()
{
WXARC_pclose(m_file->fp()); m_file->Detach();
}
PFileOutputStream::PFileOutputStream(const wxString& cmd)
: wxFFileOutputStream(WXARC_popen(cmd.mb_str(), "w" WXARC_b))
{
}
PFileOutputStream::~PFileOutputStream()
{
WXARC_pclose(m_file->fp()); m_file->Detach();
}
///////////////////////////////////////////////////////////////////////////////
// The test case
template <class ClassFactoryT>
ArchiveTestCase<ClassFactoryT>::ArchiveTestCase(
string name,
ClassFactoryT *factory,
int options,
const wxString& archiver,
const wxString& unarchiver)
:
CppUnit::TestCase(TestId::MakeId() + name),
m_factory(factory),
m_options(options),
m_timeStamp(1, wxDateTime::Mar, 2004, 12, 0),
m_id(TestId::GetId()),
m_archiver(archiver),
m_unarchiver(unarchiver)
{
wxASSERT(m_factory.get() != NULL);
}
template <class ClassFactoryT>
ArchiveTestCase<ClassFactoryT>::~ArchiveTestCase()
{
TestEntries::iterator it;
for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it)
delete it->second;
}
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::runTest()
{
TestOutputStream out(m_options);
CreateTestData();
if (m_archiver.empty())
CreateArchive(out);
else
CreateArchive(out, m_archiver);
// check archive could be created
CPPUNIT_ASSERT(out.GetLength() > 0);
TestInputStream in(out, m_id % ((m_options & PipeIn) ? 4 : 3));
TestIterator(in);
in.Rewind();
TestPairIterator(in);
in.Rewind();
TestSmartIterator(in);
in.Rewind();
TestSmartPairIterator(in);
in.Rewind();
if ((m_options & PipeIn) == 0) {
ReadSimultaneous(in);
in.Rewind();
}
ModifyArchive(in, out);
in.SetData(out);
if (m_unarchiver.empty())
ExtractArchive(in);
else
ExtractArchive(in, m_unarchiver);
// check that all the test entries were found in the archive
CPPUNIT_ASSERT(m_testEntries.empty());
}
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::CreateTestData()
{
Add("text/");
Add("text/empty", "");
Add("text/small", "Small text file for testing\n"
"archive streams in wxWidgets\n");
Add("bin/");
Add("bin/bin1000", 1000);
Add("bin/bin4095", 4095);
Add("bin/bin4096", 4096);
Add("bin/bin4097", 4097);
Add("bin/bin16384", 16384);
Add("zero/");
Add("zero/zero5", 5, 0);
Add("zero/zero1024", 1024, 109);
Add("zero/zero32768", 32768, 106);
Add("zero/zero16385", 16385, 119);
Add("empty/");
}
template <class ClassFactoryT>
TestEntry& ArchiveTestCase<ClassFactoryT>::Add(const char *name,
const char *data,
int len /*=-1*/)
{
if (len == -1)
len = strlen(data);
TestEntry*& entry = m_testEntries[wxString(name, *wxConvCurrent)];
wxASSERT(entry == NULL);
entry = new TestEntry(m_timeStamp, len, data);
m_timeStamp += wxTimeSpan(0, 1, 30);
return *entry;
}
template <class ClassFactoryT>
TestEntry& ArchiveTestCase<ClassFactoryT>::Add(const char *name,
int len /*=0*/,
int value /*=EOF*/)
{
wxCharBuffer buf(len);
for (int i = 0; i < len; i++)
buf.data()[i] = (char)(value == EOF ? rand() : value);
return Add(name, buf, len);
}
// Create an archive using the wx archive classes, write it to 'out'
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::CreateArchive(wxOutputStream& out)
{
auto_ptr<OutputStreamT> arc(m_factory->NewStream(out));
TestEntries::iterator it;
OnCreateArchive(*arc);
// We want to try creating entries in various different ways, 'choices'
// is just a number used to select between all the various possibilities.
int choices = m_id;
for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it) {
choices += 5;
TestEntry& testEntry = *it->second;
wxString name = it->first;
// It should be possible to create a directory entry just by supplying
// a name that looks like a directory, or alternatively any old name
// can be identified as a directory using SetIsDir or PutNextDirEntry
bool setIsDir = name.Last() == _T('/') && (choices & 1);
if (setIsDir)
name.erase(name.length() - 1);
// provide some context for the error message so that we know which
// iteration of the loop we were on
string error_entry((_T(" '") + name + _T("'")).mb_str());
string error_context(" failed for entry" + error_entry);
if ((choices & 2) || testEntry.IsText()) {
// try PutNextEntry(EntryT *pEntry)
auto_ptr<EntryT> entry(m_factory->NewEntry());
entry->SetName(name, wxPATH_UNIX);
if (setIsDir)
entry->SetIsDir();
entry->SetDateTime(testEntry.GetDateTime());
entry->SetSize(testEntry.GetLength());
OnCreateEntry(*arc, testEntry, entry.get());
CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context,
arc->PutNextEntry(entry.release()));
}
else {
// try the convenience methods
OnCreateEntry(*arc, testEntry);
if (setIsDir)
CPPUNIT_ASSERT_MESSAGE("PutNextDirEntry" + error_context,
arc->PutNextDirEntry(name, testEntry.GetDateTime()));
else
CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context,
arc->PutNextEntry(name, testEntry.GetDateTime(),
testEntry.GetLength()));
}
if (it->first.Last() != _T('/')) {
// for non-dirs write the data
arc->Write(testEntry.GetData(), testEntry.GetSize());
CPPUNIT_ASSERT_MESSAGE("LastWrite check" + error_context,
arc->LastWrite() == testEntry.GetSize());
// should work with or without explicit CloseEntry
if (choices & 3)
CPPUNIT_ASSERT_MESSAGE("CloseEntry" + error_context,
arc->CloseEntry());
}
CPPUNIT_ASSERT_MESSAGE("IsOk" + error_context, arc->IsOk());
}
// should work with or without explicit Close
if (m_id % 2)
CPPUNIT_ASSERT(arc->Close());
}
// Create an archive using an external archive program
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::CreateArchive(wxOutputStream& out,
const wxString& archiver)
{
// for an external archiver the test data need to be written to
// temp files
TempDir tmpdir;
// write the files
TestEntries::iterator i;
for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) {
wxFileName fn(i->first, wxPATH_UNIX);
TestEntry& entry = *i->second;
if (fn.IsDir()) {
fn.Mkdir(0777, wxPATH_MKDIR_FULL);
} else {
wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);
wxFFileOutputStream fileout(fn.GetFullPath());
fileout.Write(entry.GetData(), entry.GetSize());
}
}
for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) {
wxFileName fn(i->first, wxPATH_UNIX);
TestEntry& entry = *i->second;
wxDateTime dt = entry.GetDateTime();
#ifdef __WXMSW__
if (fn.IsDir())
entry.SetDateTime(wxDateTime());
else
#endif
fn.SetTimes(NULL, &dt, NULL);
}
if ((m_options & PipeOut) == 0) {
wxFileName fn(tmpdir.GetName());
fn.SetExt(_T("arc"));
wxString tmparc = fn.GetPath(wxPATH_GET_SEPARATOR) + fn.GetFullName();
// call the archiver to create an archive file
system(wxString::Format(archiver, tmparc.c_str()).mb_str());
// then load the archive file
{
wxFFileInputStream in(tmparc);
if (in.Ok())
out.Write(in);
}
wxRemoveFile(tmparc);
}
else {
// for the non-seekable test, have the archiver output to "-"
// and read the archive via a pipe
PFileInputStream in(wxString::Format(archiver, _T("-")));
if (in.Ok())
out.Write(in);
}
}
// Do a standard set of modification on an archive, delete an entry,
// rename an entry and add an entry
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::ModifyArchive(wxInputStream& in,
wxOutputStream& out)
{
auto_ptr<InputStreamT> arcIn(m_factory->NewStream(in));
auto_ptr<OutputStreamT> arcOut(m_factory->NewStream(out));
EntryT *pEntry;
const wxString deleteName = _T("bin/bin1000");
const wxString renameFrom = _T("zero/zero1024");
const wxString renameTo = _T("zero/newname");
const wxString newName = _T("newfile");
const char *newData = "New file added as a test\n";
arcOut->CopyArchiveMetaData(*arcIn);
while ((pEntry = arcIn->GetNextEntry()) != NULL) {
auto_ptr<EntryT> entry(pEntry);
OnSetNotifier(*entry);
wxString name = entry->GetName(wxPATH_UNIX);
// provide some context for the error message so that we know which
// iteration of the loop we were on
string error_entry((_T(" '") + name + _T("'")).mb_str());
string error_context(" failed for entry" + error_entry);
if (name == deleteName) {
TestEntries::iterator it = m_testEntries.find(name);
CPPUNIT_ASSERT_MESSAGE(
"deletion failed (already deleted?) for" + error_entry,
it != m_testEntries.end());
TestEntry *p = it->second;
m_testEntries.erase(it);
delete p;
}
else {
if (name == renameFrom) {
entry->SetName(renameTo);
TestEntries::iterator it = m_testEntries.find(renameFrom);
CPPUNIT_ASSERT_MESSAGE(
"rename failed (already renamed?) for" + error_entry,
it != m_testEntries.end());
TestEntry *p = it->second;
m_testEntries.erase(it);
m_testEntries[renameTo] = p;
}
CPPUNIT_ASSERT_MESSAGE("CopyEntry" + error_context,
arcOut->CopyEntry(entry.release(), *arcIn));
}
}
// check that the deletion and rename were done
CPPUNIT_ASSERT(m_testEntries.count(deleteName) == 0);
CPPUNIT_ASSERT(m_testEntries.count(renameFrom) == 0);
CPPUNIT_ASSERT(m_testEntries.count(renameTo) == 1);
// check that the end of the input archive was reached without error
CPPUNIT_ASSERT(arcIn->Eof());
// try adding a new entry
TestEntry& testEntry = Add(newName.mb_str(), newData);
auto_ptr<EntryT> newentry(m_factory->NewEntry());
newentry->SetName(newName);
newentry->SetDateTime(testEntry.GetDateTime());
newentry->SetSize(testEntry.GetLength());
OnCreateEntry(*arcOut, testEntry, newentry.get());
OnSetNotifier(*newentry);
CPPUNIT_ASSERT(arcOut->PutNextEntry(newentry.release()));
CPPUNIT_ASSERT(arcOut->Write(newData, strlen(newData)).IsOk());
// should work with or without explicit Close
if (m_id % 2)
CPPUNIT_ASSERT(arcOut->Close());
}
// Extract an archive using the wx archive classes
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::ExtractArchive(wxInputStream& in)
{
typedef Ptr<EntryT> EntryPtr;
typedef std::list<EntryPtr> Entries;
typedef typename Entries::iterator EntryIter;
auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
int expectedTotal = m_testEntries.size();
EntryPtr entry;
Entries entries;
if ((m_options & PipeIn) == 0)
OnArchiveExtracted(*arc, expectedTotal);
while (entry = EntryPtr(arc->GetNextEntry()), entry.get() != NULL) {
wxString name = entry->GetName(wxPATH_UNIX);
// provide some context for the error message so that we know which
// iteration of the loop we were on
string error_entry((_T(" '") + name + _T("'")).mb_str());
string error_context(" failed for entry" + error_entry);
TestEntries::iterator it = m_testEntries.find(name);
CPPUNIT_ASSERT_MESSAGE(
"archive contains an entry that shouldn't be there" + error_entry,
it != m_testEntries.end());
const TestEntry& testEntry = *it->second;
wxDateTime dt = testEntry.GetDateTime();
if (dt.IsValid())
CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context,
dt == entry->GetDateTime());
// non-seekable entries are allowed to have GetSize == wxInvalidOffset
// until the end of the entry's data has been read past
CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context,
testEntry.GetLength() == entry->GetSize() ||
((m_options & PipeIn) != 0 && entry->GetSize() == wxInvalidOffset));
CPPUNIT_ASSERT_MESSAGE(
"arc->GetLength() == entry->GetSize()" + error_context,
arc->GetLength() == entry->GetSize());
if (name.Last() != _T('/'))
{
CPPUNIT_ASSERT_MESSAGE("!IsDir" + error_context,
!entry->IsDir());
wxCharBuffer buf(testEntry.GetSize() + 1);
CPPUNIT_ASSERT_MESSAGE("Read until Eof" + error_context,
arc->Read(buf.data(), testEntry.GetSize() + 1).Eof());
CPPUNIT_ASSERT_MESSAGE("LastRead check" + error_context,
arc->LastRead() == testEntry.GetSize());
CPPUNIT_ASSERT_MESSAGE("data compare" + error_context,
!memcmp(buf.data(), testEntry.GetData(), testEntry.GetSize()));
} else {
CPPUNIT_ASSERT_MESSAGE("IsDir" + error_context, entry->IsDir());
}
// GetSize() must return the right result in all cases after all the
// data has been read
CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context,
testEntry.GetLength() == entry->GetSize());
CPPUNIT_ASSERT_MESSAGE(
"arc->GetLength() == entry->GetSize()" + error_context,
arc->GetLength() == entry->GetSize());
if ((m_options & PipeIn) == 0) {
OnEntryExtracted(*entry, testEntry, arc.get());
delete it->second;
m_testEntries.erase(it);
} else {
entries.push_back(entry);
}
}
// check that the end of the input archive was reached without error
CPPUNIT_ASSERT(arc->Eof());
// for non-seekable streams these data are only guaranteed to be
// available once the end of the archive has been reached
if (m_options & PipeIn) {
for (EntryIter i = entries.begin(); i != entries.end(); ++i) {
wxString name = (*i)->GetName(wxPATH_UNIX);
TestEntries::iterator j = m_testEntries.find(name);
OnEntryExtracted(**i, *j->second);
delete j->second;
m_testEntries.erase(j);
}
OnArchiveExtracted(*arc, expectedTotal);
}
}
// Extract an archive using an external unarchive program
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::ExtractArchive(wxInputStream& in,
const wxString& unarchiver)
{
// for an external unarchiver, unarchive to a tempdir
TempDir tmpdir;
if ((m_options & PipeIn) == 0) {
wxFileName fn(tmpdir.GetName());
fn.SetExt(_T("arc"));
wxString tmparc = fn.GetPath(wxPATH_GET_SEPARATOR) + fn.GetFullName();
if (m_options & Stub)
in.SeekI(STUB_SIZE * 2);
// write the archive to a temporary file
{
wxFFileOutputStream out(tmparc);
if (out.Ok())
out.Write(in);
}
// call unarchiver
system(wxString::Format(unarchiver, tmparc.c_str()).mb_str());
wxRemoveFile(tmparc);
}
else {
// for the non-seekable test, have the archiver extract "-" and
// feed it the archive via a pipe
PFileOutputStream out(wxString::Format(unarchiver, _T("-")));
if (out.Ok())
out.Write(in);
}
wxString dir = tmpdir.GetName();
VerifyDir(dir);
}
// Verifies the files produced by an external unarchiver are as expected
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::VerifyDir(wxString& path,
size_t rootlen /*=0*/)
{
wxDir dir;
path += wxFileName::GetPathSeparator();
int pos = path.length();
wxString name;
if (!rootlen)
rootlen = pos;
if (dir.Open(path) && dir.GetFirst(&name)) {
do {
path.replace(pos, wxString::npos, name);
name = m_factory->GetInternalName(
path.substr(rootlen, wxString::npos));
bool isDir = wxDirExists(path);
if (isDir)
name += _T("/");
// provide some context for the error message so that we know which
// iteration of the loop we were on
string error_entry((_T(" '") + name + _T("'")).mb_str());
string error_context(" failed for entry" + error_entry);
TestEntries::iterator it = m_testEntries.find(name);
CPPUNIT_ASSERT_MESSAGE(
"archive contains an entry that shouldn't be there"
+ error_entry,
it != m_testEntries.end());
const TestEntry& testEntry = *it->second;
#if 0 //ndef __WXMSW__
CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context,
testEntry.GetDateTime() ==
wxFileName(path).GetModificationTime());
#endif
if (!isDir) {
wxFFileInputStream in(path);
CPPUNIT_ASSERT_MESSAGE(
"entry not found in archive" + error_entry, in.Ok());
size_t size = (size_t)in.GetLength();
wxCharBuffer buf(size);
CPPUNIT_ASSERT_MESSAGE("Read" + error_context,
in.Read(buf.data(), size).LastRead() == size);
CPPUNIT_ASSERT_MESSAGE("size check" + error_context,
testEntry.GetSize() == size);
CPPUNIT_ASSERT_MESSAGE("data compare" + error_context,
memcmp(buf.data(), testEntry.GetData(), size) == 0);
}
else {
VerifyDir(path, rootlen);
}
delete it->second;
m_testEntries.erase(it);
}
while (dir.GetNext(&name));
}
}
// test the simple iterators that give away ownership of an entry
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::TestIterator(wxInputStream& in)
{
typedef std::list<EntryT*> ArchiveCatalog;
typedef typename ArchiveCatalog::iterator CatalogIter;
auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
size_t count = 0;
#ifdef WXARC_MEMBER_TEMPLATES
ArchiveCatalog cat((IterT)*arc, IterT());
#else
ArchiveCatalog cat;
for (IterT i(*arc); i != IterT(); ++i)
cat.push_back(*i);
#endif
for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
auto_ptr<EntryT> entry(*it);
count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
}
CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
CPPUNIT_ASSERT(count == cat.size());
}
// test the pair iterators that can be used to load a std::map or wxHashMap
// these also give away ownership of entries
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::TestPairIterator(wxInputStream& in)
{
typedef std::map<wxString, EntryT*> ArchiveCatalog;
typedef typename ArchiveCatalog::iterator CatalogIter;
auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
size_t count = 0;
#ifdef WXARC_MEMBER_TEMPLATES
ArchiveCatalog cat((PairIterT)*arc, PairIterT());
#else
ArchiveCatalog cat;
for (PairIterT i(*arc); i != PairIterT(); ++i)
cat.insert(*i);
#endif
for (CatalogIter it = cat.begin(); it != cat.end(); ++it) {
auto_ptr<EntryT> entry(it->second);
count += m_testEntries.count(entry->GetName(wxPATH_UNIX));
}
CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
CPPUNIT_ASSERT(count == cat.size());
}
// simple iterators using smart pointers, no need to worry about ownership
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::TestSmartIterator(wxInputStream& in)
{
typedef std::list<Ptr<EntryT> > ArchiveCatalog;
typedef typename ArchiveCatalog::iterator CatalogIter;
typedef wxArchiveIterator<InputStreamT, Ptr<EntryT> > Iter;
auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
#ifdef WXARC_MEMBER_TEMPLATES
ArchiveCatalog cat((Iter)*arc, Iter());
#else
ArchiveCatalog cat;
for (Iter i(*arc); i != Iter(); ++i)
cat.push_back(*i);
#endif
CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
CPPUNIT_ASSERT(m_testEntries.count((*it)->GetName(wxPATH_UNIX)));
}
// pair iterator using smart pointers
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::TestSmartPairIterator(wxInputStream& in)
{
#if defined _MSC_VER && defined _MSC_VER < 1200
// With VC++ 5.0 the '=' operator of std::pair breaks when the second
// type is Ptr<EntryT>, so this iterator can't be made to work.
(void)in;
#else
typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
typedef typename ArchiveCatalog::iterator CatalogIter;
typedef wxArchiveIterator<InputStreamT,
std::pair<wxString, Ptr<EntryT> > > PairIter;
auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
#ifdef WXARC_MEMBER_TEMPLATES
ArchiveCatalog cat((PairIter)*arc, PairIter());
#else
ArchiveCatalog cat;
for (PairIter i(*arc); i != PairIter(); ++i)
cat.insert(*i);
#endif
CPPUNIT_ASSERT(m_testEntries.size() == cat.size());
for (CatalogIter it = cat.begin(); it != cat.end(); ++it)
CPPUNIT_ASSERT(m_testEntries.count(it->second->GetName(wxPATH_UNIX)));
#endif
}
// try reading two entries at the same time
//
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::ReadSimultaneous(TestInputStream& in)
{
typedef std::map<wxString, Ptr<EntryT> > ArchiveCatalog;
typedef wxArchiveIterator<InputStreamT,
std::pair<wxString, Ptr<EntryT> > > PairIter;
// create two archive input streams
TestInputStream in2(in);
auto_ptr<InputStreamT> arc(m_factory->NewStream(in));
auto_ptr<InputStreamT> arc2(m_factory->NewStream(in2));
// load the catalog
#ifdef WXARC_MEMBER_TEMPLATES
ArchiveCatalog cat((PairIter)*arc, PairIter());
#else
ArchiveCatalog cat;
for (PairIter i(*arc); i != PairIter(); ++i)
cat.insert(*i);
#endif
// the names of two entries to read
const wxChar *name = _T("text/small");
const wxChar *name2 = _T("bin/bin1000");
// open them
typename ArchiveCatalog::iterator j;
CPPUNIT_ASSERT((j = cat.find(name)) != cat.end());
CPPUNIT_ASSERT(arc->OpenEntry(*j->second));
CPPUNIT_ASSERT((j = cat.find(name2)) != cat.end());
CPPUNIT_ASSERT(arc2->OpenEntry(*j->second));
// get pointers to the expected data
TestEntries::iterator k;
CPPUNIT_ASSERT((k = m_testEntries.find(name)) != m_testEntries.end());
TestEntry *entry = k->second;
CPPUNIT_ASSERT((k = m_testEntries.find(name2)) != m_testEntries.end());
TestEntry *entry2 = k->second;
size_t count = 0, count2 = 0;
size_t size = entry->GetSize(), size2 = entry2->GetSize();
const char *data = entry->GetData(), *data2 = entry2->GetData();
// read and check the two entries in parallel, character by character
while (arc->IsOk() || arc2->IsOk()) {
char ch = arc->GetC();
if (arc->LastRead() == 1) {
CPPUNIT_ASSERT(count < size);
CPPUNIT_ASSERT(ch == data[count++]);
}
char ch2 = arc2->GetC();
if (arc2->LastRead() == 1) {
CPPUNIT_ASSERT(count2 < size2);
CPPUNIT_ASSERT(ch2 == data2[count2++]);
}
}
CPPUNIT_ASSERT(arc->Eof());
CPPUNIT_ASSERT(arc2->Eof());
CPPUNIT_ASSERT(count == size);
CPPUNIT_ASSERT(count2 == size2);
}
// Nothing useful can be done with a generic notifier yet, so just test one
// can be set
//
template <class NotifierT, class EntryT>
class ArchiveNotifier : public NotifierT
{
public:
void OnEntryUpdated(EntryT& WXUNUSED(entry)) { }
};
template <class ClassFactoryT>
void ArchiveTestCase<ClassFactoryT>::OnSetNotifier(EntryT& entry)
{
static ArchiveNotifier<NotifierT, EntryT> notifier;
entry.SetNotifier(notifier);
}
///////////////////////////////////////////////////////////////////////////////
// An additional case to check that reading corrupt archives doesn't crash
class CorruptionTestCase : public CppUnit::TestCase
{
public:
CorruptionTestCase(std::string name,
wxArchiveClassFactory *factory,
int options)
: CppUnit::TestCase(TestId::MakeId() + name),
m_factory(factory),
m_options(options)
{ }
protected:
// the entry point for the test
void runTest();
void CreateArchive(wxOutputStream& out);
void ExtractArchive(wxInputStream& in);
auto_ptr<wxArchiveClassFactory> m_factory; // factory to make classes
int m_options; // test options
};
void CorruptionTestCase::runTest()
{
TestOutputStream out(m_options);
CreateArchive(out);
TestInputStream in(out, 0);
wxFileOffset len = in.GetLength();
// try flipping one byte in the archive
for (int pos = 0; pos < len; pos++) {
char n = in[pos];
in[pos] = ~n;
ExtractArchive(in);
in.Rewind();
in[pos] = n;
}
// try zeroing one byte in the archive
for (int pos = 0; pos < len; pos++) {
char n = in[pos];
in[pos] = 0;
ExtractArchive(in);
in.Rewind();
in[pos] = n;
}
// try chopping the archive off
for (int size = 1; size <= len; size++) {
in.Chop(size);
ExtractArchive(in);
in.Rewind();
}
}
void CorruptionTestCase::CreateArchive(wxOutputStream& out)
{
auto_ptr<wxArchiveOutputStream> arc(m_factory->NewStream(out));
arc->PutNextDirEntry(_T("dir"));
arc->PutNextEntry(_T("file"));
arc->Write(_T("foo"), 3);
}
void CorruptionTestCase::ExtractArchive(wxInputStream& in)
{
auto_ptr<wxArchiveInputStream> arc(m_factory->NewStream(in));
auto_ptr<wxArchiveEntry> entry(arc->GetNextEntry());
while (entry.get() != NULL) {
wxString name = entry->GetName();
char buf[1024];
while (arc->IsOk())
arc->Read(buf, sizeof(buf));
entry = auto_ptr<wxArchiveEntry>(arc->GetNextEntry());
}
}
///////////////////////////////////////////////////////////////////////////////
// Make the ids
int TestId::m_seed = 6219;
// static
string TestId::MakeId()
{
m_seed = (m_seed * 171) % 30269;
return string(wxString::Format(_T("%-6d"), m_seed).mb_str());
}
///////////////////////////////////////////////////////////////////////////////
// Suite base
ArchiveTestSuite::ArchiveTestSuite(string name)
: CppUnit::TestSuite("archive/" + name),
m_name(name.c_str(), *wxConvCurrent)
{
m_name = _T("wx") + m_name.Left(1).Upper() + m_name.Mid(1).Lower();
m_path.AddEnvList(_T("PATH"));
m_archivers.push_back(_T(""));
m_unarchivers.push_back(_T(""));
}
// add the command for an external archiver to the list, testing for it in
// the path first
//
void ArchiveTestSuite::AddCmd(wxArrayString& cmdlist, const wxString& cmd)
{
if (IsInPath(cmd))
cmdlist.push_back(cmd);
}
bool ArchiveTestSuite::IsInPath(const wxString& cmd)
{
wxString c = cmd.BeforeFirst(_T(' '));
#ifdef __WXMSW__
c += _T(".exe");
#endif
return !m_path.FindValidPath(c).empty();
}
// make the test suite
//
ArchiveTestSuite *ArchiveTestSuite::makeSuite()
{
typedef wxArrayString::iterator Iter;
for (int generic = 0; generic < 2; generic++)
for (Iter i = m_unarchivers.begin(); i != m_unarchivers.end(); ++i)
for (Iter j = m_archivers.begin(); j != m_archivers.end(); ++j)
for (int options = 0; options <= AllOptions; options++)
{
#ifdef WXARC_NO_POPEN
// if no popen then can't pipe in/out of archiver
if ((options & PipeIn) && !i->empty())
continue;
if ((options & PipeOut) && !j->empty())
continue;
#endif
string descr = Description(m_name, options,
generic != 0, *j, *i);
CppUnit::Test *test = makeTest(descr, options,
generic != 0, *j, *i);
if (test)
addTest(test);
}
for (int options = 0; options <= PipeIn; options += PipeIn)
{
wxObject *pObj = wxCreateDynamicObject(m_name + _T("ClassFactory"));
wxArchiveClassFactory *factory;
factory = wxDynamicCast(pObj, wxArchiveClassFactory);
if (factory) {
string descr(m_name.mb_str());
descr = "CorruptionTestCase (" + descr + ")";
if (options)
descr += " (PipeIn)";
addTest(new CorruptionTestCase(descr, factory, options));
}
}
return this;
}
CppUnit::Test *ArchiveTestSuite::makeTest(
string WXUNUSED(descr),
int WXUNUSED(options),
bool WXUNUSED(genericInterface),
const wxString& WXUNUSED(archiver),
const wxString& WXUNUSED(unarchiver))
{
return NULL;
}
// make a display string for the option bits
//
string ArchiveTestSuite::Description(const wxString& type,
int options,
bool genericInterface,
const wxString& archiver,
const wxString& unarchiver)
{
wxString descr;
if (genericInterface)
descr << _T("wxArchive (") << type << _T(")");
else
descr << type;
if (!archiver.empty()) {
const wxChar *fn = (options & PipeOut) != 0 ? _T("-") : _T("file");
descr << _T(" (") << wxString::Format(archiver, fn) << _T(")");
}
if (!unarchiver.empty()) {
const wxChar *fn = (options & PipeIn) != 0 ? _T("-") : _T("file");
descr << _T(" (") << wxString::Format(unarchiver, fn) << _T(")");
}
wxString optstr;
if ((options & PipeIn) != 0)
optstr += _T("|PipeIn");
if ((options & PipeOut) != 0)
optstr += _T("|PipeOut");
if ((options & Stub) != 0)
optstr += _T("|Stub");
if (!optstr.empty())
optstr = _T(" (") + optstr.substr(1) + _T(")");
descr << optstr;
return string(descr.mb_str());
}
///////////////////////////////////////////////////////////////////////////////
// Instantiations
template class ArchiveTestCase<wxArchiveClassFactory>;
#if wxUSE_ZIPSTREAM
#include "wx/zipstrm.h"
template class ArchiveTestCase<wxZipClassFactory>;
#endif
#endif // wxUSE_STREAMS && wxUSE_ARCHIVE_STREAMS