/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* .
**/
#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
// Must be the first to be sure to use the Orthanc framework shared library
# include
#endif
#include
#include "../Sources/Compression/HierarchicalZipWriter.h"
#include "../Sources/Compression/ZipReader.h"
#include "../Sources/OrthancException.h"
#include "../Sources/SystemToolbox.h"
#include "../Sources/TemporaryFile.h"
#include "../Sources/Toolbox.h"
using namespace Orthanc;
TEST(ZipWriter, Basic)
{
Orthanc::ZipWriter w;
w.SetOutputPath("UnitTestsResults/hello.zip");
w.Open();
w.OpenFile("world/hello");
w.Write("Hello world");
}
TEST(ZipWriter, Basic64)
{
Orthanc::ZipWriter w;
w.SetOutputPath("UnitTestsResults/hello64.zip");
w.SetZip64(true);
w.Open();
w.OpenFile("world/hello");
w.Write("Hello world");
}
TEST(ZipWriter, Exceptions)
{
Orthanc::ZipWriter w;
ASSERT_THROW(w.Open(), Orthanc::OrthancException);
w.SetOutputPath("UnitTestsResults/hello3.zip");
w.Open();
ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException);
}
TEST(ZipWriter, Append)
{
{
Orthanc::ZipWriter w;
w.SetAppendToExisting(false);
w.SetOutputPath("UnitTestsResults/append.zip");
w.Open();
w.OpenFile("world/hello");
w.Write("Hello world 1");
}
{
Orthanc::ZipWriter w;
w.SetAppendToExisting(true);
w.SetOutputPath("UnitTestsResults/append.zip");
w.Open();
w.OpenFile("world/appended");
w.Write("Hello world 2");
}
}
namespace Orthanc
{
// The namespace is necessary
// http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members
TEST(HierarchicalZipWriter, Index)
{
HierarchicalZipWriter::Index i;
ASSERT_EQ("hello", i.OpenFile("hello"));
ASSERT_EQ("hello-2", i.OpenFile("hello"));
ASSERT_EQ("coucou", i.OpenFile("coucou"));
ASSERT_EQ("hello-3", i.OpenFile("hello"));
i.OpenDirectory("coucou");
ASSERT_EQ("coucou-2/world", i.OpenFile("world"));
ASSERT_EQ("coucou-2/world-2", i.OpenFile("world"));
i.OpenDirectory("world");
ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello"));
ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello"));
i.CloseDirectory();
ASSERT_EQ("coucou-2/world-4", i.OpenFile("world"));
i.CloseDirectory();
ASSERT_EQ("coucou-3", i.OpenFile("coucou"));
ASSERT_THROW(i.CloseDirectory(), OrthancException);
}
TEST(HierarchicalZipWriter, Filenames)
{
ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric(" ÊtrE hellô "));
// The "^" character is considered as a space in DICOM
ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric(" Hel^^ ^\r\n\t^^lo \t "));
}
}
TEST(HierarchicalZipWriter, Basic)
{
static const std::string SPACES = " ";
HierarchicalZipWriter w("UnitTestsResults/hello2.zip");
w.SetCompressionLevel(0);
// Inside "/"
w.OpenFile("hello");
w.Write(SPACES + "hello\n");
w.OpenFile("hello");
w.Write(SPACES + "hello-2\n");
w.OpenDirectory("hello");
// Inside "/hello-3"
w.OpenFile("hello");
w.Write(SPACES + "hello\n");
w.OpenDirectory("hello");
w.SetCompressionLevel(9);
// Inside "/hello-3/hello-2"
w.OpenFile("hello");
w.Write(SPACES + "hello\n");
w.OpenFile("hello");
w.Write(SPACES + "hello-2\n");
w.CloseDirectory();
// Inside "/hello-3"
w.OpenFile("hello");
w.Write(SPACES + "hello-3\n");
/**
TO CHECK THE CONTENT OF THE "hello2.zip" FILE:
# unzip -v hello2.zip
=> There must be 6 files. The first 3 files must have a negative
compression ratio.
**/
}
TEST(ZipReader, Basic)
{
TemporaryFile f;
{
Orthanc::ZipWriter w;
ASSERT_EQ(0u, w.GetArchiveSize());
w.SetOutputPath(f.GetPath().c_str());
w.Open();
w.OpenFile("world/hello");
w.Write("Hello world");
ASSERT_EQ(w.GetArchiveSize(), SystemToolbox::GetFileSize(f.GetPath()));
}
ASSERT_TRUE(ZipReader::IsZipFile(f.GetPath()));
std::unique_ptr reader(ZipReader::CreateFromFile(f.GetPath()));
ASSERT_EQ(1u, reader->GetFilesCount());
std::string filename, content;
ASSERT_TRUE(reader->ReadNextFile(filename, content));
ASSERT_EQ("world/hello", filename);
ASSERT_EQ("Hello world", content);
ASSERT_FALSE(reader->ReadNextFile(filename, content));
}
TEST(ZipWriter, Stream)
{
std::string memory;
std::string large;
large.resize(4 * 65536);
for (size_t i = 0; i < large.size(); i++)
{
large[i] = rand() % 256;
}
for (int i = 0; i < 2; i++)
{
{
Orthanc::ZipWriter w;
w.SetMemoryOutput(memory, (i == 0) /* ZIP64? */);
w.Open();
w.OpenFile("world/hello");
w.Write("Hello");
w.CancelStream();
}
ASSERT_THROW(ZipReader::CreateFromMemory(memory), Orthanc::OrthancException);
memory.clear();
uint64_t archiveSize;
{
Orthanc::ZipWriter w;
ASSERT_EQ(0u, w.GetArchiveSize());
w.SetMemoryOutput(memory, (i == 0) /* ZIP64? */);
w.Open();
ASSERT_EQ(0u, w.GetArchiveSize());
w.OpenFile("world/hello");
w.Write(large);
w.OpenFile("world/hello2");
w.Write(large);
w.OpenFile("world/hello3");
w.Write("Hello world");
w.OpenFile("world/hello4");
w.Write(large);
ASSERT_TRUE(memory.empty());
uint64_t s1 = w.GetArchiveSize();
ASSERT_NE(0u, s1);
w.Close();
archiveSize = w.GetArchiveSize();
ASSERT_NE(archiveSize, s1);
ASSERT_EQ(archiveSize, w.GetArchiveSize());
}
ASSERT_EQ(archiveSize, memory.size());
std::unique_ptr reader(ZipReader::CreateFromMemory(memory));
ASSERT_EQ(4u, reader->GetFilesCount());
{
std::string filename, content;
ASSERT_TRUE(reader->ReadNextFile(filename, content));
ASSERT_EQ("world/hello", filename);
ASSERT_EQ(large.size(), content.size());
ASSERT_TRUE(memcmp(large.c_str(), content.c_str(), large.size()) == 0);
}
{
std::string filename, content;
ASSERT_TRUE(reader->ReadNextFile(filename, content));
ASSERT_EQ("world/hello2", filename);
ASSERT_EQ(large.size(), content.size());
ASSERT_TRUE(memcmp(large.c_str(), content.c_str(), large.size()) == 0);
}
{
std::string filename, content;
ASSERT_TRUE(reader->ReadNextFile(filename, content));
ASSERT_EQ("world/hello3", filename);
ASSERT_EQ("Hello world", content);
}
{
std::string filename, content;
ASSERT_TRUE(reader->ReadNextFile(filename, content));
ASSERT_EQ("world/hello4", filename);
ASSERT_EQ(large.size(), content.size());
ASSERT_TRUE(memcmp(large.c_str(), content.c_str(), large.size()) == 0);
}
{
std::string filename, content;
ASSERT_FALSE(reader->ReadNextFile(filename, content));
}
}
}
namespace Orthanc
{
// The namespace is necessary because of FRIEND_TEST
// http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members
TEST(ZipWriter, BufferWithSeek)
{
ZipWriter::BufferWithSeek buffer;
ASSERT_EQ(0u, buffer.GetSize());
std::string s;
buffer.Flush(s);
ASSERT_TRUE(s.empty());
buffer.Write("hello");
ASSERT_EQ(5u, buffer.GetSize());
ASSERT_EQ(5u, buffer.GetPosition());
buffer.Write("world");
ASSERT_EQ(10u, buffer.GetSize());
ASSERT_EQ(10u, buffer.GetPosition());
buffer.Flush(s);
ASSERT_EQ("helloworld", s);
ASSERT_EQ(0u, buffer.GetSize());
ASSERT_EQ(0u, buffer.GetPosition());
buffer.Write("hello world");
buffer.Seek(4);
ASSERT_EQ(4u, buffer.GetPosition());
buffer.Write("ab");
ASSERT_EQ(6u, buffer.GetPosition());
buffer.Flush(s);
ASSERT_EQ("hellabworld", s);
ASSERT_EQ(0u, buffer.GetPosition());
buffer.Seek(0);
ASSERT_EQ(0u, buffer.GetPosition());
buffer.Write("abc");
buffer.Write("");
ASSERT_EQ(3u, buffer.GetPosition());
buffer.Seek(3);
ASSERT_THROW(buffer.Seek(4), OrthancException);
ASSERT_EQ(3u, buffer.GetPosition());
buffer.Write("de");
buffer.Write("");
ASSERT_EQ(5u, buffer.GetPosition());
buffer.Seek(3);
buffer.Seek(3);
ASSERT_EQ(3u, buffer.GetPosition());
ASSERT_THROW(buffer.Write("def"), OrthancException);
buffer.Write("");
ASSERT_EQ(3u, buffer.GetPosition());
buffer.Write("fg");
ASSERT_EQ(5u, buffer.GetPosition());
buffer.Write("hi");
ASSERT_EQ(7u, buffer.GetPosition());
buffer.Flush(s);
ASSERT_EQ("abcfghi", s);
ASSERT_EQ(0u, buffer.GetPosition());
buffer.Write("abc");
ASSERT_EQ(3u, buffer.GetPosition());
buffer.Seek(2);
ASSERT_EQ(2u, buffer.GetPosition());
buffer.Write("z");
ASSERT_EQ(3u, buffer.GetPosition());
buffer.Seek(1);
ASSERT_EQ(1u, buffer.GetPosition());
buffer.Write("y");
ASSERT_EQ(2u, buffer.GetPosition());
buffer.Flush(s);
ASSERT_EQ("ayz", s);
ASSERT_EQ(0u, buffer.GetPosition());
ASSERT_EQ(0u, buffer.GetPosition());
buffer.Write("abc");
ASSERT_EQ(3u, buffer.GetPosition());
buffer.Seek(1);
ASSERT_EQ(1u, buffer.GetPosition());
buffer.Write("z");
ASSERT_EQ(2u, buffer.GetPosition());
buffer.Seek(3);
ASSERT_EQ(3u, buffer.GetPosition());
buffer.Write("y");
ASSERT_EQ(4u, buffer.GetPosition());
buffer.Flush(s);
ASSERT_EQ("azcy", s);
ASSERT_EQ(0u, buffer.GetPosition());
buffer.Flush(s);
ASSERT_TRUE(s.empty());
ASSERT_EQ(0u, buffer.GetPosition());
}
}