Browse Source

Trade: base structures for memory-mappable serialization format.

meshdata-cereal-killer
Vladimír Vondruš 6 years ago
parent
commit
471cef5095
  1. 2
      src/Magnum/Trade/CMakeLists.txt
  2. 94
      src/Magnum/Trade/Data.cpp
  3. 188
      src/Magnum/Trade/Data.h
  4. 2
      src/Magnum/Trade/Test/CMakeLists.txt
  5. 214
      src/Magnum/Trade/Test/DataTest.cpp
  6. 4
      src/Magnum/Trade/Trade.h

2
src/Magnum/Trade/CMakeLists.txt

@ -28,7 +28,6 @@ find_package(Corrade REQUIRED PluginManager)
set(MagnumTrade_SRCS
AbstractMaterialData.cpp
ArrayAllocator.cpp
Data.cpp
LightData.cpp
MeshObjectData2D.cpp
MeshObjectData3D.cpp
@ -41,6 +40,7 @@ set(MagnumTrade_GracefulAssert_SRCS
AbstractSceneConverter.cpp
AnimationData.cpp
CameraData.cpp
Data.cpp
ImageData.cpp
MeshData.cpp
ObjectData2D.cpp

94
src/Magnum/Trade/Data.cpp

@ -25,10 +25,17 @@
#include "Data.h"
#include <cctype>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/EnumSet.hpp>
namespace Magnum { namespace Trade {
static_assert(sizeof(DataChunkHeader) == (sizeof(void*) == 4 ? 20 : 24),
"DataChunkHeader has unexpected size");
static_assert(alignof(DataChunkHeader) == sizeof(std::size_t),
"DataChunkHeader has unexpected alignment");
Debug& operator<<(Debug& debug, const DataFlag value) {
debug << "Trade::DataFlag" << Debug::nospace;
@ -50,6 +57,93 @@ Debug& operator<<(Debug& debug, const DataFlags value) {
DataFlag::Mutable});
}
namespace {
Debug& printFourCC(Debug& debug, UnsignedInt value, const char* name) {
debug << name << Debug::nospace;
for(std::size_t i = 0; i != 4; ++i) {
if(i) debug << Debug::nospace << ",";
const int c = value & 255;
if(std::isprint(c)) {
const char data[] = {'\'', char(c), '\'', '\0'};
debug << data;
} else {
debug << reinterpret_cast<void*>(c);
}
value >>= 8;
}
return debug << Debug::nospace << ")";
}
}
Debug& operator<<(Debug& debug, const DataChunkType value) {
return printFourCC(debug, Containers::enumCastUnderlyingType(value), "Trade::DataChunkType(");
}
Debug& operator<<(Debug& debug, const DataChunkSignature value) {
return printFourCC(debug, Containers::enumCastUnderlyingType(value), "Trade::DataChunkSignature(");
}
namespace {
constexpr DataChunkHeader DataChunkHeaderPrefix{
128, {'\x0a'}, {'\x0d', '\x0a'}, DataChunkSignature::Current, 0, 0,
/* Type and size isn't checked when validating and gets overwritten
when serializing */
DataChunkType{}, 0
};
static_assert(DataChunkHeaderPrefix.version & 0x80,
"version needs the high bit set to prevent detection as a text file");
}
bool isDataChunk(Containers::ArrayView<const void> data) {
return data && data.size() >= sizeof(DataChunkHeader) &&
std::memcmp(data.data(), &DataChunkHeaderPrefix, 10) == 0 &&
reinterpret_cast<const DataChunkHeader*>(data.data())->size <= data.size();
}
const DataChunkHeader* dataChunkHeaderDeserialize(const Containers::ArrayView<const void> data) {
if(data.size() < sizeof(DataChunkHeader)) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected at least" << sizeof(DataChunkHeader) << "bytes for a header but got" << data.size();
return nullptr;
}
const auto& header = *reinterpret_cast<const DataChunkHeader*>(data.data());
if(header.version != DataChunkHeaderPrefix.version) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected version" << DataChunkHeaderPrefix.version << "but got" << header.version;
return nullptr;
}
if(header.signature != DataChunkSignature::Current) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected signature" << DataChunkSignature::Current << "but got" << header.signature;
return nullptr;
}
if(std::memcmp(data.data(), &DataChunkHeaderPrefix, 10) != 0) {
Error{} << "Trade::dataChunkHeaderDeserialize(): invalid header check bytes";
return nullptr;
}
if(header.size > data.size()) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected at least" << header.size << "bytes but got" << data.size();
return nullptr;
}
return reinterpret_cast<const DataChunkHeader*>(data.data());
}
std::size_t dataChunkHeaderSerializeInto(const Containers::ArrayView<char> out, const DataChunkType type, const UnsignedShort typeVersion) {
CORRADE_ASSERT(out.size() >= sizeof(DataChunkHeader),
"Trade::dataChunkHeaderSerializeInto(): data too small, expected at least" << sizeof(DataChunkHeader) << "bytes but got" << out.size(), {});
auto& header = *reinterpret_cast<DataChunkHeader*>(out.data());
header = DataChunkHeaderPrefix;
header.typeVersion = typeVersion;
header.type = type;
header.size = out.size();
return sizeof(DataChunkHeader);
}
namespace Implementation {
void nonOwnedArrayDeleter(char*, std::size_t) { /* does nothing */ }
}

188
src/Magnum/Trade/Data.h

@ -26,11 +26,13 @@
*/
/** @file
* @brief Enum @ref Magnum::Trade::DataFlag, enum set @ref Magnum::Trade::DataFlags
* @brief Struct @ref Magnum::Trade::DataChunkHeader, enum @ref Magnum::Trade::DataFlag, @ref Magnum::Trade::DataChunkSignature, @ref Magnum::Trade::DataChunkType, enum set @ref Magnum::Trade::DataFlags, function @ref Magnum::Trade::isDataChunk(), @ref Magnum::Trade::dataChunkHeaderDeserialize(), @ref Magnum::Trade::dataChunkHeaderSerializeInto()
* @m_since_latest
*/
#include <cstddef>
#include <Corrade/Containers/EnumSet.h>
#include <Corrade/Utility/Endianness.h>
#include "Magnum/Magnum.h"
#include "Magnum/Trade/visibility.h"
@ -85,6 +87,190 @@ CORRADE_ENUMSET_OPERATORS(DataFlags)
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlags value);
/**
@brief Memory-mappable data chunk type
@m_since_latest
A [FourCC](https://en.wikipedia.org/wiki/FourCC)-like identifier of the data
contained in the chunk. Used together with @ref DataChunkHeader::typeVersion to
identify version of a particular chunk type.
@section Trade-DataChunkType-custom Custom data chunk types
All identifiers starting with an uppercase leter are reserved for Magnum
itself, custom application-specific data types should use a lowercase first
letter instead. Casing of the three remaining characters doesn't have any
specified effect in the current version of the header. It doesn't need to be
alphanumeric either, but for additional versioning of a particular chunk type
it's recommended to use @ref DataChunkHeader::typeVersion, keeping the chunk
type FourCC clearly recognizable.
*/
enum class DataChunkType: UnsignedInt {
/**
* Serialized @ref MeshData. The letters `Mesh`.
*
* Current version is @cpp 0 @ce.
*/
Mesh = Utility::Endianness::fourCC('M', 'e', 's', 'h'),
#if 0
/* None of these used yet, here just to lay out the naming scheme */
Animation = Utility::Endianness::fourCC('A', 'n', 'i', 'm'),
Camera = Utility::Endianness::fourCC('C', 'a', 'm', 0),
Image1D = Utility::Endianness::fourCC('I', 'm', 'g', '1'),
Image2D = Utility::Endianness::fourCC('I', 'm', 'g', '2'),
Image3D = Utility::Endianness::fourCC('I', 'm', 'g', '3'),
Light = Utility::Endianness::fourCC('L', 'i', 'g', 't'),
Material = Utility::Endianness::fourCC('M', 't', 'l', 0),
Scene = Utility::Endianness::fourCC('S', 'c', 'n', 0),
Texture = Utility::Endianness::fourCC('T', 'e', 'x', 0)
#endif
};
/**
@debugoperatorenum{DataChunkType}
@m_since_latest
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataChunkType value);
/**
@brief Memory-mappable data chunk signature
@m_since_latest
Reads as `BLOB` letters for a Little-Endian 64 bit data chunk. For Big-Endian
the order is reversed (thus `BOLB`), 32-bit data have the `L` letter lowercase.
@see @ref DataChunkHeader::signature
*/
enum class DataChunkSignature: UnsignedInt {
/** Little-Endian 32-bit data. The letters `BlOB`. */
Little32 = Utility::Endianness::fourCC('B', 'l', 'O', 'B'),
/** Little-Endian 64-bit data. The letters `BLOB`. */
Little64 = Utility::Endianness::fourCC('B', 'L', 'O', 'B'),
/** Big-Endian 32-bit data. The letters `BOlB`. */
Big32 = Utility::Endianness::fourCC('B', 'O', 'l', 'B'),
/** Big-Endian 64-bit data. The letters `BOLB`. */
Big64 = Utility::Endianness::fourCC('B', 'O', 'L', 'B'),
/** Signature matching this platform. Alias to one of the above. */
Current
#ifndef DOXYGEN_GENERATING_OUTPUT
=
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(std::size_t) == 8 ? Little64 : Little32
#else
sizeof(std::size_t) == 8 ? Big64 : Big32
#endif
#endif
};
/**
@debugoperatorenum{DataChunkSignature}
@m_since_latest
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataChunkSignature value);
/**
@brief Header for memory-mappable data chunks
@m_since_latest
Since the goal of the serialization format is to be a direct equivalent to the
in-memory data layout, there's four different variants of the header based on
whether it's running on a 32-bit or 64-bit system and whether the machine is
Little- or Big-Endian. A 64-bit variant of the header has 24 bytes to support
data larger than 4 GB, the 32-bit variant is 20 bytes. Apart from the @ref size
member, the header is designed to contain the same amount of information on
both, and its size is chosen so the immediately following data can be aligned
to either 4 or 8 bytes without needing to add extra padding.
The header contents are as follows, vaguely inspired by the
[PNG file header](https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header).
All fields except @ref typeVersion and @ref size (marked with
@m_class{m-label m-primary} **E**) are stored in an endian-independent way,
otherwise the endian matches the signature field.
@m_class{m-row m-container-inflate}
@parblock
@m_class{m-fullwidth}
Byte offset | Byte size&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | Member | Contents
----------- | --------- | ------------- | -------------------------------------
0 | 1 | @ref DataChunkHeader::version "version" | Header version. Has the high bit set to prevent the file from being detected as text. Currently set to @cpp 128 @ce.
1 | 1 | @ref eolUnix | Unix EOL (LF, @cpp '\x0a' @ce), to detect unwanted Unix-to-DOS line ending conversion
2 | 2 | @ref eolDos | DOS EOL (CR+LF, @cpp '\x0d', '\x0a' @ce), to detect unwanted DOS-to-Unix line ending conversion
4 | 4 | @ref signature | File signature. Differs based on bitness and endianness, see @ref DataChunkSignature for more information.
8 | 2 | @ref zero | Two zero bytes (@cpp '\x00', '\x00' @ce), to prevent the data from being treated and copied as a null-terminated (wide) string.
10 | 2 @m_class{m-label m-primary} **E** | @ref typeVersion | Data chunk type version. Use is chunk-specific, see @ref DataChunkType for more information.
12 | 4 | @ref type | Data chunk type, see @ref DataChunkType for more information
16 | 4 or 8 @m_class{m-label m-primary} **E** | @ref size | Data chunk size, including the header size. Stored in size matching the signature field.
@endparblock
For a particular header variant the first 10 bytes is static and thus can be
used for file validation. After the header are directly the chunk data. For performance reasons it's recommended to have the data padded to be a multiple
of 4 or 8 bytes to ensure the immediately following chunk is correctly aligned
as well, but it's not a strict recommendation and not enforced in any way in
current version of the format.
Current version of the header doesn't have any checksum field in order to make
it easy to modify the data in-place, this might change in the future.
@see @ref DataChunkSignature, @ref DataChunkType, @ref isDataChunk(),
@ref dataChunkHeaderDeserialize(), @ref dataChunkHeaderSerializeInto()
*/
struct DataChunkHeader {
UnsignedByte version; /**< @brief Header version */
char eolUnix[1]; /**< @brief Unix EOL */
char eolDos[2]; /**< @brief Dos EOL */
DataChunkSignature signature; /**< @brief Signature */
UnsignedShort zero; /**< @brief Two zero bytes */
UnsignedShort typeVersion; /**< @brief Chunk type version */
DataChunkType type; /**< @brief Chunk type */
std::size_t size; /**< @brief Chunk size */
};
/**
@brief Check if given data blob is a valid data chunk
@m_since_latest
Returns @cpp true @ce if @p data is a valid @ref DataChunkHeader, matches
current platform and @p data is large enough to contain the whole chunk,
@cpp false @ce otherwise. The function doesn't print any diagnostic messages on
validation failure, use @ref dataChunkHeaderDeserialize() instead if you need
to know why.
*/
MAGNUM_TRADE_EXPORT bool isDataChunk(Containers::ArrayView<const void> data);
/**
@brief Try to deserialize a data chunk from a memory-mappable representation
@m_since_latest
Checks that @p data is large enough to contain a valid data chunk, validates
the header and then returns @p data reinterpreted as a @ref DataChunkHeader
pointer. On failure prints an error message and returns @cpp nullptr @ce.
@see @ref isDataChunk(), @ref dataChunkHeaderSerializeInto()
*/
MAGNUM_TRADE_EXPORT const DataChunkHeader* dataChunkHeaderDeserialize(Containers::ArrayView<const void> data);
/**
@brief Serialize a data chunk header into existing array
@param[out] out Where to write the output
@param[out] type Data chunk type
@param[out] typeVersion Data chunk type version
@return Number of bytes written. Same as size of @ref DataChunkHeader.
@m_since_latest
Expects that @p data is at least the size of @ref DataChunkHeader. Fills in
@ref DataChunkHeader::typeVersion and @ref DataChunkHeader::type with passed
values used in constructor, and @ref DataChunkHeader::size with @p data size.
@see @ref dataChunkHeaderDeserialize()
*/
MAGNUM_TRADE_EXPORT std::size_t dataChunkHeaderSerializeInto(Containers::ArrayView<char> out, DataChunkType type, UnsignedShort typeVersion);
namespace Implementation {
/* Used internally by MeshData */
MAGNUM_TRADE_EXPORT void nonOwnedArrayDeleter(char*, std::size_t);

2
src/Magnum/Trade/Test/CMakeLists.txt

@ -48,7 +48,7 @@ target_include_directories(TradeAbstractSceneConverterTest PRIVATE ${CMAKE_CURRE
corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib)

214
src/Magnum/Trade/Test/DataTest.cpp

@ -24,8 +24,12 @@
*/
#include <sstream>
#include <Corrade/Containers/Array.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#include "Magnum/Trade/Data.h"
@ -34,13 +38,207 @@ namespace Magnum { namespace Trade { namespace Test { namespace {
struct DataTest: TestSuite::Tester {
explicit DataTest();
void dataChunkHeaderDeserialize();
void dataChunkHeaderDeserializeInvalid();
void dataChunkHeaderSerialize();
void dataChunkHeaderSerializeTooShort();
void debugDataFlag();
void debugDataFlags();
void debugDataChunkType();
void debugDataChunkSignature();
};
constexpr char Data32[]{
'\x80', '\x0a', '\x0d', '\x0a', 'B',
#ifndef CORRADE_TARGET_BIG_ENDIAN
'l', 'O',
#else
'O', 'l',
#endif
'B', 0, 0,
#ifndef CORRADE_TARGET_BIG_ENDIAN
42, 0,
#else
0, 42,
#endif
'W', 'a', 'v', 'e',
#ifndef CORRADE_TARGET_BIG_ENDIAN
20 + 5, 0, 0, 0,
#else
0, 0, 0, 20 + 5,
#endif
'h', 'e', 'l', 'l', 'o'
};
constexpr char Data64[]{
'\x80', '\x0a', '\x0d', '\x0a', 'B',
#ifndef CORRADE_TARGET_BIG_ENDIAN
'L', 'O',
#else
'O', 'L',
#endif
'B', 0, 0,
#ifndef CORRADE_TARGET_BIG_ENDIAN
42, 0,
#else
0, 42,
#endif
'W', 'a', 'v', 'e',
#ifndef CORRADE_TARGET_BIG_ENDIAN
24 + 5, 0, 0, 0, 0, 0, 0, 0,
#else
0, 0, 0, 0, 0, 0, 0, 24 + 5,
#endif
'h', 'e', 'l', 'l', 'o'
};
constexpr Containers::ArrayView<const char> Data = sizeof(void*) == 4 ?
Containers::arrayView(Data32) : Containers::arrayView(Data64);
const struct {
const char* name;
std::size_t size;
std::size_t offset;
Containers::Array<char> replace;
const char* message;
} DataChunkDeserializeInvalidData[] {
{"too short header",
sizeof(void*) == 4 ? 19 : 23, 0, {},
sizeof(void*) == 4 ?
"expected at least 20 bytes for a header but got 19" :
"expected at least 24 bytes for a header but got 23"},
{"too short chunk",
sizeof(void*) == 4 ? 24 : 28, 0, {},
sizeof(void*) == 4 ?
"expected at least 25 bytes but got 24" :
"expected at least 29 bytes but got 28"},
{"wrong version",
0, 0, Containers::array<char>({'\x7f'}),
"expected version 128 but got 127"},
{"invalid signature",
0, 4,
/* Using the 32-bit signature on 64-bit and vice versa */
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(void*) == 4 ?
Containers::array<char>({'B', 'L', 'O', 'B'}) :
Containers::array<char>({'B', 'l', 'O', 'B'}),
sizeof(void*) == 4 ?
"expected signature Trade::DataChunkSignature('B', 'l', 'O', 'B') but got Trade::DataChunkSignature('B', 'L', 'O', 'B')" :
"expected signature Trade::DataChunkSignature('B', 'L', 'O', 'B') but got Trade::DataChunkSignature('B', 'l', 'O', 'B')"
#else
sizeof(void*) == 4 ?
Containers::array<char>({'B', 'O', 'L', 'B'}) :
Containers::array<char>({'B', 'O', 'l', 'B'}),
sizeof(void*) == 4 ?
"expected signature Trade::DataChunkSignature('B', 'O', 'l', 'B') but got Trade::DataChunkSignature('B', 'O', 'L', 'B')" :
"expected signature Trade::DataChunkSignature('B', 'O', 'L', 'B') but got Trade::DataChunkSignature('B', 'O', 'l', 'B')"
#endif
},
{"invalid check bytes",
0, 8, Containers::array<char>({1, 0}),
"invalid header check bytes"},
};
constexpr struct {
const char* name;
std::size_t size;
} DataChunkSerializeData[] {
{"no extra data", sizeof(DataChunkHeader)},
{"1735 bytes extra data", sizeof(DataChunkHeader) + 1735}
};
DataTest::DataTest() {
addTests({&DataTest::dataChunkHeaderDeserialize});
addInstancedTests({&DataTest::dataChunkHeaderDeserializeInvalid},
Containers::arraySize(DataChunkDeserializeInvalidData));
addInstancedTests({&DataTest::dataChunkHeaderSerialize},
Containers::arraySize(DataChunkSerializeData));
addTests({&DataTest::dataChunkHeaderSerializeTooShort});
addTests({&DataTest::debugDataFlag,
&DataTest::debugDataFlags});
&DataTest::debugDataFlags,
&DataTest::debugDataChunkType,
&DataTest::debugDataChunkSignature});
}
void DataTest::dataChunkHeaderDeserialize() {
CORRADE_VERIFY(isDataChunk(Data));
const DataChunkHeader* chunk = Trade::dataChunkHeaderDeserialize(Data);
CORRADE_VERIFY(chunk);
}
void DataTest::dataChunkHeaderDeserializeInvalid() {
auto&& data = DataChunkDeserializeInvalidData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> blob{Containers::NoInit, Data.size()};
Utility::copy(Data, blob);
Containers::ArrayView<char> view = blob;
if(data.size) view = view.prefix(data.size);
if(data.replace) Utility::copy(data.replace, view.slice(data.offset, data.offset + data.replace.size()));
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!isDataChunk(view));
CORRADE_VERIFY(!Trade::dataChunkHeaderDeserialize(view));
CORRADE_COMPARE(out.str(),
Utility::formatString("Trade::dataChunkHeaderDeserialize(): {}\n", data.message));
}
void DataTest::dataChunkHeaderSerialize() {
auto&& data = DataChunkSerializeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> out{Containers::NoInit, data.size};
std::size_t size = dataChunkHeaderSerializeInto(out, DataChunkType(Utility::Endianness::fourCC('r', 't', 'F', 'm')), 0xfeed);
CORRADE_COMPARE(size, sizeof(DataChunkHeader));
#ifndef CORRADE_TARGET_BIG_ENDIAN
if(sizeof(void*) == 4) CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'l', 'O', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
char(data.size & 0xff), char(data.size >> 8 & 0xff), 0, 0,
}), TestSuite::Compare::Container);
else CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'L', 'O', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
char(data.size & 0xff), char(data.size >> 8 & 0xff), 0, 0, 0, 0, 0, 0
}), TestSuite::Compare::Container);
#else
if(sizeof(void*) == 4) CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'O', 'l', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
0, 0, char(data.size >> 8 & 0xff), char(data.size & 0xff)
}), TestSuite::Compare::Container);
else CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'O', 'L', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
0, 0, 0, 0, 0, 0, char(data.size >> 8 & 0xff), char(data.size & 0xff)
}), TestSuite::Compare::Container);
#endif
}
void DataTest::dataChunkHeaderSerializeTooShort() {
std::ostringstream out;
Error redirectError{&out};
char data[sizeof(DataChunkHeader) - 1];
dataChunkHeaderSerializeInto(data, DataChunkType{}, 0);
CORRADE_COMPARE(out.str(), sizeof(void*) == 4 ?
"Trade::dataChunkHeaderSerializeInto(): data too small, expected at least 20 bytes but got 19\n" :
"Trade::dataChunkHeaderSerializeInto(): data too small, expected at least 24 bytes but got 23\n");
}
void DataTest::debugDataFlag() {
@ -57,6 +255,20 @@ void DataTest::debugDataFlags() {
CORRADE_COMPARE(out.str(), "Trade::DataFlag::Owned|Trade::DataFlag::Mutable Trade::DataFlags{}\n");
}
void DataTest::debugDataChunkType() {
std::ostringstream out;
Debug{&out} << DataChunkType(Utility::Endianness::fourCC('M', 's', 'h', '\xab')) << DataChunkType{};
CORRADE_COMPARE(out.str(), "Trade::DataChunkType('M', 's', 'h', 0xab) Trade::DataChunkType(0x0, 0x0, 0x0, 0x0)\n");
}
void DataTest::debugDataChunkSignature() {
std::ostringstream out;
Debug{&out} << DataChunkSignature::Little64 << DataChunkSignature{};
CORRADE_COMPARE(out.str(), "Trade::DataChunkSignature('B', 'L', 'O', 'B') Trade::DataChunkSignature(0x0, 0x0, 0x0, 0x0)\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::DataTest)

4
src/Magnum/Trade/Trade.h

@ -62,6 +62,10 @@ class CameraData;
enum class DataFlag: UnsignedByte;
typedef Containers::EnumSet<DataFlag> DataFlags;
struct DataChunkHeader;
class DataChunk;
enum class DataChunkSignature: UnsignedInt;
enum class DataChunkType: UnsignedInt;
template<UnsignedInt> class ImageData;
typedef ImageData<1> ImageData1D;

Loading…
Cancel
Save