Merge pull request #2060 from Exiv2/main_bmpTests

Extend unit tests for BMP & Datasets
main
Luis Díaz Más 3 years ago committed by GitHub
commit ed09d9f017
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,6 +17,7 @@ jobs:
run: |
sudo apt-get install ninja-build
pip3 install conan==1.43.0
pip3 install gcovr
- name: Conan common config
run: |
@ -32,16 +33,27 @@ jobs:
- name: Build
run: |
cd build
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON -DEXIV2_ENABLE_PNG=ON -DEXIV2_ENABLE_WEBREADY=ON -DEXIV2_ENABLE_CURL=ON -DEXIV2_BUILD_UNIT_TESTS=ON -DEXIV2_ENABLE_BMFF=ON -DEXIV2_TEAM_WARNINGS_AS_ERRORS=ON -DBUILD_WITH_COVERAGE=ON -DCMAKE_INSTALL_PREFIX=install ..
cd build && \
cmake -GNinja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_SHARED_LIBS=ON \
-DEXIV2_ENABLE_PNG=ON \
-DEXIV2_ENABLE_WEBREADY=ON \
-DEXIV2_ENABLE_CURL=ON \
-DEXIV2_BUILD_UNIT_TESTS=ON \
-DEXIV2_ENABLE_BMFF=ON \
-DEXIV2_TEAM_WARNINGS_AS_ERRORS=ON \
-DEXIV2_BUILD_SAMPLES=ON \
-DBUILD_WITH_COVERAGE=ON \
-DCMAKE_INSTALL_PREFIX=install \
.. && \
cmake --build .
- name: Tests + Upload coverage
run: |
cd build
ctest --output-on-failure
pip install gcovr
gcovr -r ./../ -x --exclude-unreachable-branches --exclude-throw-branches -o coverage.xml .
gcovr --root .. --object-dir . --exclude-directories xmpsdk --exclude-directories unitTests --exclude-directories samples --exclude '.*xmpsdk.*' --exclude '.*unitTests.*' --exclude '.*samples.*' --exclude-unreachable-branches --exclude-throw-branches --xml -o coverage.xml
curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --import
curl -Os https://uploader.codecov.io/latest/linux/codecov
curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM
@ -49,7 +61,8 @@ jobs:
gpg --verify codecov.SHA256SUM.sig codecov.SHA256SUM
shasum -a 256 -c codecov.SHA256SUM
chmod +x codecov
./codecov -f build/coverage.xml
ls -lh
./codecov -f coverage.xml
special_releaseValgrind:
name: 'Ubuntu 20.04 - GCC - Release+Valgrind'

@ -14,6 +14,7 @@ if(BUILD_WITH_COVERAGE)
COMMAND ${GCOVR} --root ${CMAKE_SOURCE_DIR} --object-dir=${CMAKE_BINARY_DIR} --html --html-details -o coverage_output/coverage.html
--exclude-directories xmpsdk --exclude-directories unitTests --exclude-directories samples
--exclude '.*xmpsdk.*' --exclude '.*unitTests.*' --exclude '.*samples.*'
--exclude-unreachable-branches --exclude-throw-branches
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

@ -79,25 +79,17 @@ namespace Exiv2 {
//! @name Manipulators
//@{
void readMetadata() override;
/*!
@brief Todo: Write metadata back to the image. This method is not
yet(?) implemented. Calling it will throw an Error(kerWritingImageFormatUnsupported).
*/
/// @throws Error(kerWritingImageFormatUnsupported).
void writeMetadata() override;
/*!
@brief Todo: Not supported yet(?). Calling this function will throw
an instance of Error(kerInvalidSettingForImage).
*/
/// @throws Error(kerInvalidSettingForImage)
void setExifData(const ExifData& exifData) override;
/*!
@brief Todo: Not supported yet(?). Calling this function will throw
an instance of Error(kerInvalidSettingForImage).
*/
/// @throws Error(kerInvalidSettingForImage)
void setIptcData(const IptcData& iptcData) override;
/*!
@brief Not supported. Calling this function will throw an instance
of Error(kerInvalidSettingForImage).
*/
/// @throws Error(kerInvalidSettingForImage)
void setComment(const std::string& comment) override;
//@}
@ -105,7 +97,6 @@ namespace Exiv2 {
//@{
std::string mimeType() const override;
//@}
}; // class BmpImage
// *****************************************************************************

@ -97,6 +97,7 @@ namespace Exiv2 {
static constexpr uint16_t UNO = 100;
static constexpr uint16_t ARMId = 120;
static constexpr uint16_t ARMVersion = 122;
static constexpr uint16_t RecordVersion = 0;
static constexpr uint16_t ObjectType = 3;
static constexpr uint16_t ObjectAttribute = 4;
@ -172,6 +173,7 @@ namespace Exiv2 {
dataset.
*/
static std::string dataSetName(uint16_t number, uint16_t recordId);
/*!
@brief Return the title (label) of the dataset.
@param number The dataset number
@ -179,6 +181,7 @@ namespace Exiv2 {
@return The title (label) of the dataset
*/
static const char* dataSetTitle(uint16_t number, uint16_t recordId);
/*!
@brief Return the description of the dataset.
@param number The dataset number
@ -186,6 +189,7 @@ namespace Exiv2 {
@return The description of the dataset
*/
static const char* dataSetDesc(uint16_t number, uint16_t recordId);
/*!
@brief Return the Photoshop name of a given dataset.
@param number The dataset number
@ -194,6 +198,7 @@ namespace Exiv2 {
string if Photoshop does not use the dataset.
*/
static const char* dataSetPsName(uint16_t number, uint16_t recordId);
/*!
@brief Check if a given dataset is repeatable
@param number The dataset number
@ -201,6 +206,7 @@ namespace Exiv2 {
@return true if the given dataset is repeatable otherwise false
*/
static bool dataSetRepeatable(uint16_t number, uint16_t recordId);
/*!
@brief Return the dataSet number for dataset name and record id
@ -212,8 +218,10 @@ namespace Exiv2 {
@throw Error if the \em dataSetName or \em recordId are invalid
*/
static uint16_t dataSet(const std::string& dataSetName, uint16_t recordId);
//! Return the type for dataSet number and Record id
static TypeId dataSetType(uint16_t number, uint16_t recordId);
/*!
@brief Return the name of the Record
@param recordId The record id
@ -222,24 +230,14 @@ namespace Exiv2 {
unknown record.
*/
static std::string recordName(uint16_t recordId);
/*!
@brief Return the description of a record
@param recordId Record Id number
@return the description of the Record
*/
static const char* recordDesc(uint16_t recordId);
/*!
@brief Return the Id number of a record
@param recordName Name of a record type
@return the Id number of a Record
@throw Error if the record is not known;
*/
static uint16_t recordId(const std::string& recordName);
//! Return read-only list of built-in Envelope Record datasets
static const DataSet* envelopeRecordList();
//! Return read-only list of built-in Application2 Record datasets
static const DataSet* application2RecordList();
//! Print a list of all dataSets to output stream
static void dataSetList(std::ostream& os);
private:
@ -247,7 +245,6 @@ namespace Exiv2 {
static int dataSetIdx(const std::string& dataSetName, uint16_t recordId);
static const DataSet* const records_[];
static const RecordInfo recordInfo_[];
}; // class IptcDataSets
@ -278,17 +275,11 @@ namespace Exiv2 {
IptcKey(uint16_t tag, uint16_t record);
//! Copy constructor
IptcKey(const IptcKey& rhs);
IptcKey& operator=(const IptcKey& rhs) = delete;
//! Destructor
~IptcKey() override = default;
//@}
//! @name Manipulators
//@{
/*!
@brief Assignment operator.
*/
IptcKey& operator=(const IptcKey& rhs);
//@}
//! @name Accessors
//@{
@ -331,10 +322,6 @@ namespace Exiv2 {
//! Internal virtual copy constructor.
IptcKey* clone_() const override;
// DATA
#ifndef SWIG
static constexpr auto familyName_ = "Iptc";
#endif
uint16_t tag_; //!< Tag value
uint16_t record_; //!< Record value
std::string key_; //!< Key

@ -368,9 +368,8 @@ namespace Exiv2 {
encoded. Initially, it is not set (\em invalidByteOrder).
*/
ByteOrder byteOrder() const;
/*!
@brief Check if the Image instance is valid. Use after object
construction.
/*! @brief Check if the Image instance is valid. Use after object construction.
@return true if the Image is in a valid state.
*/
bool good() const;

@ -45,6 +45,8 @@ namespace Exiv2 {
/*!
@brief An IPTC metadatum ("dataset"), consisting of an IptcKey and a
Value and methods to manipulate these.
This is referred in the standard as a property.
*/
class EXIV2API Iptcdatum : public Metadatum {
public:
@ -156,8 +158,7 @@ namespace Exiv2 {
typedef std::vector<Iptcdatum> IptcMetadata;
/*!
@brief A container for IPTC data. This is a top-level class of
the %Exiv2 library.
@brief A container for IPTC data. This is a top-level class of the %Exiv2 library.
Provide high-level access to the IPTC data of an image:
- read IPTC information from JPEG files

@ -47,24 +47,22 @@ namespace Exiv2
std::string BmpImage::mimeType() const
{
return "image/x-ms-bmp";
// "image/bmp" is a Generic Bitmap
return "image/x-ms-bmp"; // Microsoft Bitmap
}
void BmpImage::setExifData(const ExifData& /*exifData*/)
{
// Todo: implement me!
throw(Error(kerInvalidSettingForImage, "Exif metadata", "BMP"));
}
void BmpImage::setIptcData(const IptcData& /*iptcData*/)
{
// Todo: implement me!
throw(Error(kerInvalidSettingForImage, "IPTC metadata", "BMP"));
}
void BmpImage::setComment(const std::string& /*comment*/)
{
// not supported
throw(Error(kerInvalidSettingForImage, "Image comment", "BMP"));
}
@ -77,9 +75,11 @@ namespace Exiv2
throw Error(kerDataSourceOpenFailed, io_->path(), strError());
}
IoCloser closer(*io_);
// Ensure that this is the correct image type
if (!isBmpType(*io_, false)) {
if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
if (io_->error() || io_->eof())
throw Error(kerFailedToReadImageData);
throw Error(kerNotAnImage, "BMP");
}
clearMetadata();
@ -105,7 +105,7 @@ namespace Exiv2
46 4 bytes color count
50 4 bytes important colors number of "important" colors
*/
byte buf[54];
byte buf[26];
if (io_->read(buf, sizeof(buf)) == sizeof(buf)) {
pixelWidth_ = getLong(buf + 18, littleEndian);
pixelHeight_ = getLong(buf + 22, littleEndian);
@ -114,7 +114,7 @@ namespace Exiv2
void BmpImage::writeMetadata()
{
// Todo: implement me!
/// \todo implement me!
throw(Error(kerWritingImageFormatUnsupported, "BMP"));
}

@ -34,12 +34,15 @@
#include <iostream>
#include <iomanip>
#include <regex>
#include <sstream>
// *****************************************************************************
// class member definitions
namespace Exiv2 {
constexpr RecordInfo IptcDataSets::recordInfo_[] = {
constexpr const char *familyName_{"Iptc"};
constexpr RecordInfo recordInfo_[] = {
{IptcDataSets::invalidRecord, "(invalid)", N_("(invalid)")},
{IptcDataSets::envelope, "Envelope", N_("IIM envelope record")},
{IptcDataSets::application2, "Application2", N_("IIM application record 2")},
@ -125,11 +128,6 @@ namespace Exiv2 {
"(Invalid)", false, false, 0, 0, Exiv2::unsignedShort, IptcDataSets::envelope, ""},
};
const DataSet* IptcDataSets::envelopeRecordList()
{
return envelopeRecord;
}
constexpr DataSet application2Record[] = {
{IptcDataSets::RecordVersion, "RecordVersion", N_("Record Version"),
N_("A binary number identifying the version of the Information "
@ -402,16 +400,26 @@ namespace Exiv2 {
false, false, 0, 0, Exiv2::unsignedShort, IptcDataSets::application2, ""},
};
const DataSet* IptcDataSets::application2RecordList()
uint16_t recordId(const std::string& recordName)
{
return application2Record;
uint16_t i;
for (i = IptcDataSets::application2; i > 0; --i) {
if (recordInfo_[i].name_ == recordName)
break;
}
if (i == 0) {
if (!isHex(recordName, 4, "0x"))
throw Error(kerInvalidRecord, recordName);
std::istringstream is(recordName);
is >> std::hex >> i;
}
return i;
}
constexpr DataSet unknownDataSet{0xffff, "Unknown dataset", N_("Unknown dataset"),
N_("Unknown dataset"),
false, true, 0, 0xffffffff, Exiv2::string,
IptcDataSets::invalidRecord,
N_("Unknown dataset"),};
constexpr DataSet unknownDataSet{
0xffff, "Unknown dataset", N_("Unknown dataset"), N_("Unknown dataset"), false, true, 0,
0xffffffff, Exiv2::string, IptcDataSets::invalidRecord, N_("Unknown dataset"),
};
// Dataset lookup lists.This is an array with pointers to one list per IIM4 Record.
// The record id is used as the index into the array.
@ -424,26 +432,26 @@ namespace Exiv2 {
int IptcDataSets::dataSetIdx(uint16_t number, uint16_t recordId)
{
if( recordId != envelope && recordId != application2 ) return -1;
const DataSet* dataSet = records_[recordId];
if (dataSet == nullptr)
if (recordId != envelope && recordId != application2)
return -1;
const DataSet* dataSet = records_[recordId];
int idx;
for (idx = 0; dataSet[idx].number_ != number; ++idx) {
if (dataSet[idx].number_ == 0xffff) return -1;
if (dataSet[idx].number_ == 0xffff)
return -1;
}
return idx;
}
int IptcDataSets::dataSetIdx(const std::string& dataSetName, uint16_t recordId)
{
if( recordId != envelope && recordId != application2 ) return -1;
const DataSet* dataSet = records_[recordId];
if (dataSet == nullptr)
if (recordId != envelope && recordId != application2)
return -1;
const DataSet* dataSet = records_[recordId];
int idx;
for (idx = 0; dataSet[idx].name_ != dataSetName; ++idx) {
if (dataSet[idx].number_ == 0xffff) return -1;
if (dataSet[idx].number_ == 0xffff)
return -1;
}
return idx;
}
@ -451,60 +459,64 @@ namespace Exiv2 {
TypeId IptcDataSets::dataSetType(uint16_t number, uint16_t recordId)
{
int idx = dataSetIdx(number, recordId);
if (idx == -1) return unknownDataSet.type_;
if (idx == -1)
return unknownDataSet.type_;
return records_[recordId][idx].type_;
}
std::string IptcDataSets::dataSetName(uint16_t number, uint16_t recordId)
{
int idx = dataSetIdx(number, recordId);
if (idx != -1) return records_[recordId][idx].name_;
if (idx != -1)
return records_[recordId][idx].name_;
std::ostringstream os;
os << "0x" << std::setw(4) << std::setfill('0') << std::right
<< std::hex << number;
os << "0x" << std::setw(4) << std::setfill('0') << std::right << std::hex << number;
return os.str();
}
const char* IptcDataSets::dataSetTitle(uint16_t number, uint16_t recordId)
{
int idx = dataSetIdx(number, recordId);
if (idx == -1) return unknownDataSet.title_;
if (idx == -1)
return unknownDataSet.title_;
return records_[recordId][idx].title_;
}
const char* IptcDataSets::dataSetDesc(uint16_t number, uint16_t recordId)
{
int idx = dataSetIdx(number, recordId);
if (idx == -1) return unknownDataSet.desc_;
if (idx == -1)
return unknownDataSet.desc_;
return records_[recordId][idx].desc_;
}
const char* IptcDataSets::dataSetPsName(uint16_t number, uint16_t recordId)
{
int idx = dataSetIdx(number, recordId);
if (idx == -1) return unknownDataSet.photoshop_;
if (idx == -1)
return unknownDataSet.photoshop_;
return records_[recordId][idx].photoshop_;
}
bool IptcDataSets::dataSetRepeatable(uint16_t number, uint16_t recordId)
{
int idx = dataSetIdx(number, recordId);
if (idx == -1) return unknownDataSet.repeatable_;
if (idx == -1)
return unknownDataSet.repeatable_;
return records_[recordId][idx].repeatable_;
}
uint16_t IptcDataSets::dataSet(const std::string& dataSetName,
uint16_t recordId)
uint16_t IptcDataSets::dataSet(const std::string& dataSetName, uint16_t recordId)
{
uint16_t dataSet = 0;
int idx = dataSetIdx(dataSetName, recordId);
if (idx != -1) {
// dataSetIdx checks the range of recordId
dataSet = records_[recordId][idx].number_;
}
else {
if (!isHex(dataSetName, 4, "0x")) throw Error(kerInvalidDataset, dataSetName);
} else {
if (!isHex(dataSetName, 4, "0x"))
throw Error(kerInvalidDataset, dataSetName);
std::istringstream is(dataSetName);
is >> std::hex >> dataSet;
}
@ -518,8 +530,7 @@ namespace Exiv2 {
}
std::ostringstream os;
os << "0x" << std::setw(4) << std::setfill('0') << std::right
<< std::hex << recordId;
os << "0x" << std::setw(4) << std::setfill('0') << std::right << std::hex << recordId;
return os.str();
}
@ -531,19 +542,6 @@ namespace Exiv2 {
return recordInfo_[recordId].desc_;
}
uint16_t IptcDataSets::recordId(const std::string& recordName)
{
uint16_t i;
for (i = application2; i > 0; --i) {
if (recordInfo_[i].name_ == recordName) break;
}
if (i == 0) {
if (!isHex(recordName, 4, "0x")) throw Error(kerInvalidRecord, recordName);
std::istringstream is(recordName);
is >> std::hex >> i;
}
return i;
}
void IptcDataSets::dataSetList(std::ostream& os)
{
@ -552,36 +550,22 @@ namespace Exiv2 {
os << record[j] << "\n";
}
}
} // IptcDataSets::dataSetList
}
IptcKey::IptcKey(std::string key) : key_(std::move(key))
{
decomposeKey();
}
IptcKey::IptcKey(uint16_t tag, uint16_t record)
: tag_(tag), record_(record)
IptcKey::IptcKey(uint16_t tag, uint16_t record) : tag_(tag), record_(record)
{
makeKey();
}
IptcKey::IptcKey(const IptcKey& rhs)
: tag_(rhs.tag_)
, record_(rhs.record_)
, key_(rhs.key_)
IptcKey::IptcKey(const IptcKey& rhs) : tag_(rhs.tag_), record_(rhs.record_), key_(rhs.key_)
{
}
IptcKey& IptcKey::operator=(const IptcKey& rhs)
{
if (this == &rhs) return *this;
Key::operator=(rhs);
tag_ = rhs.tag_;
record_ = rhs.record_;
key_ = rhs.key_;
return *this;
}
std::string IptcKey::key() const
{
return key_;
@ -634,25 +618,27 @@ namespace Exiv2 {
void IptcKey::decomposeKey()
{
// Check that the key has the expected format with RE
static const std::regex re(R"((\w+)(\.\w+){2})");
std::smatch sm;
if (!std::regex_match(key_, sm, re)) {
throw Error(kerInvalidKey, key_);
}
// Get the family name, record name and dataSet name parts of the key
std::string::size_type pos1 = key_.find('.');
if (pos1 == std::string::npos) throw Error(kerInvalidKey, key_);
std::string familyName = key_.substr(0, pos1);
auto posDot1 = key_.find('.');
auto posDot2 = key_.find('.', posDot1+1);
const std::string familyName = key_.substr(0, posDot1);
if (0 != strcmp(familyName.c_str(), familyName_)) {
throw Error(kerInvalidKey, key_);
}
std::string::size_type pos0 = pos1 + 1;
pos1 = key_.find('.', pos0);
if (pos1 == std::string::npos) throw Error(kerInvalidKey, key_);
std::string recordName = key_.substr(pos0, pos1 - pos0);
if (recordName.empty())
throw Error(kerInvalidKey, key_);
std::string dataSetName = key_.substr(pos1 + 1);
if (dataSetName.empty())
throw Error(kerInvalidKey, key_);
std::string recordName = key_.substr(posDot1+1, posDot2 - posDot1 - 1);
std::string dataSetName = key_.substr(posDot2+1);
// Use the parts of the key to find dataSet and recordId
uint16_t recId = IptcDataSets::recordId(recordName);
uint16_t recId = recordId(recordName);
uint16_t dataSet = IptcDataSets::dataSet(dataSetName, recId);
// Possibly translate hex name parts (0xabcd) to real names
@ -662,13 +648,12 @@ namespace Exiv2 {
tag_ = dataSet;
record_ = recId;
key_ = familyName + "." + recordName + "." + dataSetName;
} // IptcKey::decomposeKey
}
void IptcKey::makeKey()
{
key_ = std::string(familyName_)
+ "." + IptcDataSets::recordName(record_)
+ "." + IptcDataSets::dataSetName(tag_, record_);
key_ = std::string(familyName_) + "." + IptcDataSets::recordName(record_) + "." +
IptcDataSets::dataSetName(tag_, record_);
}
// *************************************************************************
@ -676,27 +661,21 @@ namespace Exiv2 {
std::ostream& operator<<(std::ostream& os, const DataSet& dataSet)
{
std::ios::fmtflags f( os.flags() );
std::ios::fmtflags f(os.flags());
IptcKey iptcKey(dataSet.number_, dataSet.recordId_);
os << dataSet.name_ << ", "
<< std::dec << dataSet.number_ << ", "
<< "0x" << std::setw(4) << std::setfill('0')
<< std::right << std::hex << dataSet.number_ << ", "
<< IptcDataSets::recordName(dataSet.recordId_) << ", "
<< std::boolalpha << dataSet.mandatory_ << ", "
<< dataSet.repeatable_ << ", "
<< std::dec << dataSet.minbytes_ << ", "
<< dataSet.maxbytes_ << ", "
<< iptcKey.key() << ", "
<< TypeInfo::typeName(
IptcDataSets::dataSetType(dataSet.number_,
dataSet.recordId_)) << ", ";
os << dataSet.name_ << ", " << std::dec << dataSet.number_ << ", "
<< "0x" << std::setw(4) << std::setfill('0') << std::right << std::hex << dataSet.number_ << ", "
<< IptcDataSets::recordName(dataSet.recordId_) << ", " << std::boolalpha << dataSet.mandatory_ << ", "
<< dataSet.repeatable_ << ", " << std::dec << dataSet.minbytes_ << ", " << dataSet.maxbytes_ << ", "
<< iptcKey.key() << ", " << TypeInfo::typeName(IptcDataSets::dataSetType(dataSet.number_, dataSet.recordId_))
<< ", ";
// CSV encoded I am \"dead\" beat" => "I am ""dead"" beat"
char Q = '"';
os << Q;
for ( size_t i = 0 ; i < ::strlen(dataSet.desc_) ; i++ ) {
for (size_t i = 0; i < ::strlen(dataSet.desc_); i++) {
char c = dataSet.desc_[i];
if ( c == Q ) os << Q;
if (c == Q)
os << Q;
os << c;
}
os << Q;

@ -55,11 +55,11 @@ namespace {
{ Exiv2::kerNotAnImage,
N_("This does not look like a %1 image") }, // %1=Image type
{ Exiv2::kerInvalidDataset,
N_("Invalid dataset name `%1'") }, // %1=dataset name
N_("Invalid dataset name '%1'") }, // %1=dataset name
{ Exiv2::kerInvalidRecord,
N_("Invalid record name `%1'") }, // %1=record name
N_("Invalid record name '%1'") }, // %1=record name
{ Exiv2::kerInvalidKey,
N_("Invalid key `%1'") }, // %1=key
N_("Invalid key '%1'") }, // %1=key
{ Exiv2::kerInvalidTag,
N_("Invalid tag name or ifdId `%1', ifdId %2") }, // %1=tag name, %2=ifdId
{ Exiv2::kerValueNotSet,

@ -741,7 +741,8 @@ namespace Exiv2 {
bool Image::good() const
{
if (io_->open() != 0) return false;
if (io_->open() != 0)
return false;
IoCloser closer(*io_);
return ImageFactory::checkType(imageType_, *io_, false);
}
@ -809,7 +810,7 @@ namespace Exiv2 {
return r->isThisType_(io, advance);
}
return false;
} // ImageFactory::checkType
}
int ImageFactory::getType(const std::string& path)
{

@ -2,6 +2,8 @@ find_package(GTest REQUIRED)
add_executable(unit_tests
mainTestRunner.cpp
test_bmpimage.cpp
test_datasets.cpp
test_DateValue.cpp
test_TimeValue.cpp
test_XmpKey.cpp
@ -12,6 +14,7 @@ add_executable(unit_tests
test_futils.cpp
test_helper_functions.cpp
test_image_int.cpp
test_IptcKey.cpp
test_pngimage.cpp
test_safe_op.cpp
test_slice.cpp

@ -0,0 +1,116 @@
#include <gtest/gtest.h>
#include <exiv2/datasets.hpp>
#include <exiv2/error.hpp>
using namespace Exiv2;
TEST(IptcKey, creationWithNonValidStringFormatThrows)
{
try {
IptcKey key("Yeah");
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidKey, e.code());
ASSERT_STREQ("Invalid key 'Yeah'", e.what());
}
}
TEST(IptcKey, creationWithNonValidRecordNameThrows)
{
try {
IptcKey key("Iptc.WrongRecordName.ModelVersion");
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidRecord, e.code());
ASSERT_STREQ("Invalid record name 'WrongRecordName'", e.what());
}
}
TEST(IptcKey, creationWithNonValidDatasetNameThrows)
{
try {
IptcKey key("Iptc.Envelope.WrongDataset");
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidDataset, e.code());
ASSERT_STREQ("Invalid dataset name 'WrongDataset'", e.what());
}
}
TEST(IptcKey, creationWithNonValidFamiltyNameThrows)
{
try {
IptcKey key("JOJO.Envelope.WrongDataset");
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidKey, e.code());
ASSERT_STREQ("Invalid key 'JOJO.Envelope.WrongDataset'", e.what());
}
}
TEST(IptcKey, creationWithValidStringDoesNotThrow)
{
ASSERT_NO_THROW(IptcKey ("Iptc.Envelope.ModelVersion"));
}
TEST(IptcKey, copyConstructor)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
IptcKey keyCopy (key);
}
TEST(IptcKey, clone)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
auto keyClone = key.clone();
ASSERT_EQ("Iptc.Envelope.ModelVersion", keyClone->key());
}
TEST(IptcKey, keyReturnsTheFullString)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_EQ("Iptc.Envelope.ModelVersion", key.key());
}
TEST(IptcKey, familyNameReturnsTheFullString)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_STREQ("Iptc", key.familyName());
}
TEST(IptcKey, groupNameReturnsTheRecordName)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_EQ("Envelope", key.groupName());
}
TEST(IptcKey, recordNameReturnsTheRecordName)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_EQ("Envelope", key.recordName());
}
TEST(IptcKey, tagNameReturnsTheDatasetName)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_EQ("ModelVersion", key.tagName());
}
TEST(IptcKey, tagLabelReturnsTheDatasetTitle)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_EQ("Model Version", key.tagLabel());
}
TEST(IptcKey, tag)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_EQ(IptcDataSets::ModelVersion, key.tag());
}
TEST(IptcKey, record)
{
IptcKey key ("Iptc.Envelope.ModelVersion");
ASSERT_EQ(IptcDataSets::envelope, key.record());
}

@ -0,0 +1,196 @@
#include <gtest/gtest.h>
#include <array>
#include <exiv2/bmpimage.hpp>
using namespace Exiv2;
TEST(BmpImage, canBeOpenedWithEmptyMemIo)
{
auto memIo = std::make_unique<MemIo>();
ASSERT_NO_THROW(BmpImage bmp(std::move(memIo)));
}
TEST(BmpImage, mimeTypeIsBmp)
{
auto memIo = std::make_unique<MemIo>();
BmpImage bmp(std::move(memIo));
ASSERT_EQ("image/x-ms-bmp", bmp.mimeType());
}
TEST(BmpImage, writeMetadataIsNotImplemented)
{
auto memIo = std::make_unique<MemIo>();
BmpImage bmp(std::move(memIo));
try {
bmp.writeMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerWritingImageFormatUnsupported, e.code());
ASSERT_STREQ("Writing to BMP images is not supported", e.what());
}
}
TEST(BmpImage, setExitDataIsNotImplemented)
{
auto memIo = std::make_unique<MemIo>();
BmpImage bmp(std::move(memIo));
try {
ExifData data;
bmp.setExifData(data);
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidSettingForImage, e.code());
ASSERT_STREQ("Setting Exif metadata in BMP images is not supported", e.what());
}
}
TEST(BmpImage, setIptcDataIsNotImplemented)
{
auto memIo = std::make_unique<MemIo>();
BmpImage bmp(std::move(memIo));
try {
IptcData data;
bmp.setIptcData(data);
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidSettingForImage, e.code());
ASSERT_STREQ("Setting IPTC metadata in BMP images is not supported", e.what());
}
}
TEST(BmpImage, setCommentIsNotImplemented)
{
auto memIo = std::make_unique<MemIo>();
BmpImage bmp(std::move(memIo));
try {
bmp.setComment("random comment");
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidSettingForImage, e.code());
ASSERT_STREQ("Setting Image comment in BMP images is not supported", e.what());
}
}
TEST(BmpImage, readMetadataReadsImageDimensionsWhenDataIsAvailable)
{
const std::array<unsigned char, 26> header{
'B', 'M', // Signature off:0 size:2
0x4E, 0x47, 0x0D, 0x0A, // Size of the BMP file in bytes off:2, size:4
0x1A, 0x0A, // Reserved off:6, size:2
0x00, 0x00, // Reserved off:8, size:2
0x00, 0x00, 0x00, 0x00, // Offset of the byte where the bitmap image data can be found off:10, size:4
0x00, 0x00, 0x00, 0x00, // Size of this header off:14, size:4
0x00, 0x05, 0x00, 0x00, // The bitmap width in pixels (unsigned 16 bit) off:18, size:4
0x20, 0x03, 0x00, 0x00, // The bitmap height in pixels (unsigned 16 bit) off:22, size:4
};
auto memIo = std::make_unique<MemIo>(header.data(), static_cast<long>(header.size()));
BmpImage bmp(std::move(memIo));
ASSERT_NO_THROW(bmp.readMetadata());
ASSERT_EQ(1280, bmp.pixelWidth());
ASSERT_EQ(800, bmp.pixelHeight());
}
TEST(BmpImage, readMetadataThrowsWhenImageIsNotBMP)
{
const std::array<unsigned char, 26> header{
'B', 'A', // Signature off:0 size:2
0x4E, 0x47, 0x0D, 0x0A, // Size of the BMP file in bytes off:2, size:4
0x1A, 0x0A, // Reserved off:6, size:2
0x00, 0x00, // Reserved off:8, size:2
0x00, 0x00, 0x00, 0x00, // Offset of the byte where the bitmap image data can be found off:10, size:4
0x00, 0x00, 0x00, 0x00, // Size of this header off:14, size:4
0x00, 0x05, 0x00, 0x00, // The bitmap width in pixels (unsigned 16 bit) off:18, size:4
0x20, 0x03, 0x00, 0x00, // The bitmap height in pixels (unsigned 16 bit) off:22, size:4
};
auto memIo = std::make_unique<MemIo>(header.data(), static_cast<long>(header.size()));
BmpImage bmp(std::move(memIo));
try {
bmp.readMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerNotAnImage, e.code());
ASSERT_STREQ("This does not look like a BMP image", e.what());
}
}
TEST(BmpImage, readMetadataThrowsWhenThereIsNotEnoughInfoToRead)
{
const std::array<unsigned char, 1> header{'B'};
auto memIo = std::make_unique<MemIo>(header.data(), static_cast<long>(header.size()));
BmpImage bmp(std::move(memIo));
try {
bmp.readMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerFailedToReadImageData, e.code());
ASSERT_STREQ("Failed to read image data", e.what());
}
}
TEST(BmpImage, readMetadataThrowsWhenIoCannotBeOpened)
{
auto fileIo = std::make_unique<FileIo>("NonExistingPath.png");
BmpImage bmp(std::move(fileIo));
try {
bmp.readMetadata();
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerDataSourceOpenFailed, e.code());
}
}
TEST(newBmpInstance, createsValidInstace)
{
const std::array<unsigned char, 14> bitmapHeader{
'B', 'M', // Signature
0x4E, 0x47, 0x0D, 0x0A, // Size of the BMP file in bytes
0x1A, 0x0A, // Reserved
0x00, 0x00, // Reserved
0x00, 0x00, 0x00, 0x00 // Offset of the byte where the bitmap image data can be found
};
auto memIo = std::make_unique<MemIo>(bitmapHeader.data(), static_cast<long>(bitmapHeader.size()));
auto img = newBmpInstance(std::move(memIo), false);
ASSERT_TRUE(img->good());
}
TEST(newBmpInstance, createsInvalidInstaceWithNonExistingFilePath)
{
auto fileIo = std::make_unique<FileIo>("NonExistingPath.png");
auto img = newBmpInstance(std::move(fileIo), false);
ASSERT_FALSE(img);
}
TEST(isBmpType, withValidSignatureReturnsTrue)
{
const std::array<unsigned char, 14> bitmapHeader{
'B', 'M', // Signature
0x4E, 0x47, 0x0D, 0x0A, // Size of the BMP file in bytes
0x1A, 0x0A, // Reserved
0x00, 0x00, // Reserved
0x00, 0x00, 0x00, 0x00 // Offset of the byte where the bitmap image data can be found
};
MemIo memIo(bitmapHeader.data(), static_cast<long>(bitmapHeader.size()));
ASSERT_TRUE(isBmpType(memIo, false));
}
TEST(isBmpType, withInvalidSignatureReturnsFalse)
{
const std::array<unsigned char, 14> bitmapHeader{
'B', 'A', // Signature
0x4E, 0x47, 0x0D, 0x0A, // Size of the BMP file in bytes
0x1A, 0x0A, // Reserved
0x00, 0x00, // Reserved
0x00, 0x00, 0x00, 0x00 // Offset of the byte where the bitmap image data can be found
};
MemIo memIo(bitmapHeader.data(), static_cast<long>(bitmapHeader.size()));
ASSERT_FALSE(isBmpType(memIo, false));
}

@ -0,0 +1,218 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <exiv2/datasets.hpp>
#include <exiv2/error.hpp>
#include <array>
#include <sstream>
using namespace Exiv2;
using ::testing::StartsWith;
TEST(IptcDataSets, dataSetName_returnsValidNamesWhenRequestingNumbersAvailableInEnvelopeRecord)
{
ASSERT_EQ("ModelVersion", IptcDataSets::dataSetName(IptcDataSets::ModelVersion, IptcDataSets::envelope));
ASSERT_EQ("Destination", IptcDataSets::dataSetName(IptcDataSets::Destination, IptcDataSets::envelope));
ASSERT_EQ("FileFormat", IptcDataSets::dataSetName(IptcDataSets::FileFormat, IptcDataSets::envelope));
ASSERT_EQ("FileVersion", IptcDataSets::dataSetName(IptcDataSets::FileVersion, IptcDataSets::envelope));
ASSERT_EQ("ServiceId", IptcDataSets::dataSetName(IptcDataSets::ServiceId, IptcDataSets::envelope));
ASSERT_EQ("EnvelopeNumber", IptcDataSets::dataSetName(IptcDataSets::EnvelopeNumber, IptcDataSets::envelope));
}
TEST(IptcDataSets, dataSetName_returnsValidNamesWhenRequestingNumbersAvailableInApplicationRecord)
{
ASSERT_EQ("ObjectType", IptcDataSets::dataSetName(IptcDataSets::ObjectType, IptcDataSets::application2));
ASSERT_EQ("ObjectAttribute", IptcDataSets::dataSetName(IptcDataSets::ObjectAttribute, IptcDataSets::application2));
}
TEST(IptcDataSets, dataSetName_returnsWrongNamesWhenRequestingNumbersNotAvailableInEnvelopeRecord)
{
ASSERT_EQ("0x0003", IptcDataSets::dataSetName(IptcDataSets::ObjectType, IptcDataSets::envelope));
ASSERT_EQ("0x0004", IptcDataSets::dataSetName(IptcDataSets::ObjectAttribute, IptcDataSets::envelope));
}
TEST(IptcDataSets, dataSetTitle_returnsValidTitleWhenRequestingNumbersAvailableInRecord)
{
ASSERT_STREQ("Model Version", IptcDataSets::dataSetTitle(IptcDataSets::ModelVersion, IptcDataSets::envelope));
ASSERT_STREQ("Destination", IptcDataSets::dataSetTitle(IptcDataSets::Destination, IptcDataSets::envelope));
ASSERT_STREQ("File Format", IptcDataSets::dataSetTitle(IptcDataSets::FileFormat, IptcDataSets::envelope));
ASSERT_STREQ("Object Type", IptcDataSets::dataSetTitle(IptcDataSets::ObjectType, IptcDataSets::application2));
ASSERT_STREQ("Object Attribute",
IptcDataSets::dataSetTitle(IptcDataSets::ObjectAttribute, IptcDataSets::application2));
}
TEST(IptcDataSets, dataSetTitle_returnsUnknownStringWhenRequestingNumbersNotAvailableInEnvelopeRecord)
{
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetTitle(IptcDataSets::ObjectType, IptcDataSets::envelope));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetTitle(IptcDataSets::ObjectAttribute, IptcDataSets::envelope));
}
// Unfortunately, some constants such as ModelVersion, Destination or FileFormat has the same values as other constants
// available for other records (RecordVersion, ObjectName & SuppCategory respectively)
// TEST(IptcDataSets, dataSetTitle_returnsUnknownStringWhenRequestingNumbersNotAvailableInApplicationRecord)
//{
// ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetTitle(IptcDataSets::ModelVersion, IptcDataSets::application2));
// ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetTitle(IptcDataSets::Destination, IptcDataSets::application2));
// ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetTitle(IptcDataSets::FileFormat, IptcDataSets::application2));
//}
TEST(IptcDataSets, dataSetTitle_returnsUnknownStringWhenRequestingNumbersNotAvailableInApplicationRecord)
{
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetTitle(1, IptcDataSets::envelope));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetTitle(2, IptcDataSets::envelope));
}
// ----------------------
TEST(IptcDataSets, dataSetDescription_returnsValidDescriptionWhenRequestingNumbersAvailableInRecord)
{
ASSERT_THAT(IptcDataSets::dataSetDesc(IptcDataSets::ModelVersion, IptcDataSets::envelope),
StartsWith("A binary number identifying the version of the Information Interchange Model"));
ASSERT_THAT(IptcDataSets::dataSetDesc(IptcDataSets::FileFormat, IptcDataSets::envelope),
StartsWith("A binary number representing the file format. The file format must be registered with"));
ASSERT_THAT(IptcDataSets::dataSetDesc(IptcDataSets::RecordVersion, IptcDataSets::application2),
StartsWith("A binary number identifying the version of the Information Interchange Model"));
ASSERT_THAT(IptcDataSets::dataSetDesc(IptcDataSets::ObjectType, IptcDataSets::application2),
StartsWith("The Object Type is used to distinguish between different types of objects within the IIM"));
}
TEST(IptcDataSets, dataSetDescription_returnsUnknownStringWhenRequestingNumbersNotAvailableInRecord)
{
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetDesc(1, IptcDataSets::envelope));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetDesc(2, IptcDataSets::envelope));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetDesc(1, IptcDataSets::application2));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetDesc(2, IptcDataSets::application2));
}
// ----------------------
TEST(IptcDataSets, dataSetPsName_returnsValidPsNameWhenRequestingNumbersAvailableInRecord)
{
ASSERT_STREQ("", IptcDataSets::dataSetPsName(IptcDataSets::FileFormat, IptcDataSets::envelope));
ASSERT_STREQ("Document Title", IptcDataSets::dataSetPsName(IptcDataSets::ObjectName, IptcDataSets::application2));
ASSERT_STREQ("Urgency", IptcDataSets::dataSetPsName(IptcDataSets::Urgency, IptcDataSets::application2));
}
TEST(IptcDataSets, dataSetPsName_returnsUnknownStringWhenRequestingNumbersNotAvailableInRecord)
{
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetPsName(1, IptcDataSets::envelope));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetPsName(2, IptcDataSets::envelope));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetPsName(1, IptcDataSets::application2));
ASSERT_STREQ("Unknown dataset", IptcDataSets::dataSetPsName(2, IptcDataSets::application2));
}
// ----------------------
TEST(IptcDataSets, dataSetRepeatable_returnsExpectedValueNameWhenRequestingNumbersAvailableInRecord)
{
ASSERT_TRUE(IptcDataSets::dataSetRepeatable(IptcDataSets::Destination, IptcDataSets::envelope));
ASSERT_FALSE(IptcDataSets::dataSetRepeatable(IptcDataSets::FileFormat, IptcDataSets::envelope));
ASSERT_FALSE(IptcDataSets::dataSetRepeatable(IptcDataSets::ObjectType, IptcDataSets::application2));
ASSERT_TRUE(IptcDataSets::dataSetRepeatable(IptcDataSets::ObjectAttribute, IptcDataSets::application2));
}
/// \todo check if we want to return true in this case or throw an exception ...
TEST(IptcDataSets, dataSetRepeatable_returnsTrueWhenRequestingNumbersNotAvailableInRecord)
{
ASSERT_TRUE(IptcDataSets::dataSetRepeatable(1, IptcDataSets::envelope));
ASSERT_TRUE(IptcDataSets::dataSetRepeatable(2, IptcDataSets::envelope));
ASSERT_TRUE(IptcDataSets::dataSetRepeatable(1, IptcDataSets::application2));
ASSERT_TRUE(IptcDataSets::dataSetRepeatable(2, IptcDataSets::application2));
}
// ----------------------
TEST(IptcDataSets, dataSet_returnsExpectedValueWhenRequestingValidDatasetName)
{
ASSERT_EQ(IptcDataSets::ModelVersion, IptcDataSets::dataSet("ModelVersion", IptcDataSets::envelope));
ASSERT_EQ(IptcDataSets::FileFormat, IptcDataSets::dataSet("FileFormat", IptcDataSets::envelope));
ASSERT_EQ(IptcDataSets::RecordVersion, IptcDataSets::dataSet("RecordVersion", IptcDataSets::application2));
ASSERT_EQ(IptcDataSets::FixtureId, IptcDataSets::dataSet("FixtureId", IptcDataSets::application2));
}
TEST(IptcDataSets, dataSet_throwWithNonExistingDatasetName)
{
try {
IptcDataSets::dataSet("NonExistingName", IptcDataSets::envelope);
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidDataset, e.code());
ASSERT_STREQ("Invalid dataset name 'NonExistingName'", e.what());
}
}
/// \todo Weird error reporting here. It should indicate that the record specified does not exist
TEST(IptcDataSets, dataSet_throwWithNonExistingRecordId)
{
try {
IptcDataSets::dataSet("ModelVersion", 5);
FAIL();
} catch (const Exiv2::Error& e) {
ASSERT_EQ(kerInvalidDataset, e.code());
ASSERT_STREQ("Invalid dataset name 'ModelVersion'", e.what());
}
}
// ----------------------
TEST(IptcDataSets, dataSetType_returnsExpectedTypeWhenRequestingValidDataset)
{
ASSERT_EQ(unsignedShort, IptcDataSets::dataSetType(IptcDataSets::ModelVersion, IptcDataSets::envelope));
ASSERT_EQ(Exiv2::string, IptcDataSets::dataSetType(IptcDataSets::Destination, IptcDataSets::envelope));
ASSERT_EQ(unsignedShort, IptcDataSets::dataSetType(IptcDataSets::RecordVersion, IptcDataSets::application2));
ASSERT_EQ(Exiv2::string, IptcDataSets::dataSetType(IptcDataSets::ObjectType, IptcDataSets::application2));
}
/// \todo probably better to throw exception here?
TEST(IptcDataSets, dataSetType_returnsStringTypeWhenRecordIdDoesNotExist)
{
ASSERT_EQ(Exiv2::string, IptcDataSets::dataSetType(1, 5));
}
// ----------------------
TEST(IptcDataSets, recordName_returnsExpectedNameWhenRequestingValidRecordId)
{
ASSERT_EQ("Envelope", IptcDataSets::recordName(IptcDataSets::envelope));
ASSERT_EQ("Application2", IptcDataSets::recordName(IptcDataSets::application2));
}
TEST(IptcDataSets, recordName_returnsHexStringWhenRecordIdDoesNotExist)
{
ASSERT_EQ("0x0000", IptcDataSets::recordName(0));
ASSERT_EQ("0x0003", IptcDataSets::recordName(3));
}
// ----------------------
TEST(IptcDataSets, recordDesc_returnsExpectedDescriptionWhenRequestingValidRecordId)
{
ASSERT_STREQ("IIM envelope record", IptcDataSets::recordDesc(IptcDataSets::envelope));
ASSERT_STREQ("IIM application record 2", IptcDataSets::recordDesc(IptcDataSets::application2));
}
TEST(IptcDataSets, recordDesc_)
{
ASSERT_STREQ("Unknown dataset", IptcDataSets::recordDesc(0));
ASSERT_STREQ("Unknown dataset", IptcDataSets::recordDesc(3));
}
// ----------------------
TEST(IptcDataSets, dataSetLists_printDatasetsIntoOstream)
{
std::ostringstream stream;
ASSERT_NO_THROW(IptcDataSets::dataSetList(stream));
ASSERT_FALSE(stream.str().empty());
}

@ -158,6 +158,14 @@ TEST(PngImage, cannotWriteMetadataToEmptyIo)
}
}
TEST(PngImage, canWriteMetadataFromCreatedPngImage)
{
auto memIo = std::make_unique<MemIo>();
const bool create {true};
PngImage png(std::move(memIo), create);
ASSERT_NO_THROW(png.writeMetadata());
}
TEST(PngImage, cannotWriteMetadataToIoWhichCannotBeOpened)
{
auto memIo = std::make_unique<FileIo>("NonExistingPath.png");

Loading…
Cancel
Save