Mercurial > hg > orthanc
changeset 971:509e146c3cb3 plugins
integration mainline->plugins
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 30 Jun 2014 13:36:01 +0200 |
parents | 743a75b14bef (current diff) 1a3817d84f39 (diff) |
children | ab6475e33473 |
files | CMakeLists.txt Core/Enumerations.h Core/Toolbox.cpp Core/Toolbox.h NEWS OrthancServer/ParsedDicomFile.cpp UnitTestsSources/DicomMap.cpp UnitTestsSources/FileStorage.cpp UnitTestsSources/FromDcmtk.cpp UnitTestsSources/JpegLossless.cpp UnitTestsSources/Lua.cpp UnitTestsSources/MemoryCache.cpp UnitTestsSources/MultiThreading.cpp UnitTestsSources/Png.cpp UnitTestsSources/RestApi.cpp UnitTestsSources/SQLite.cpp UnitTestsSources/SQLiteChromium.cpp UnitTestsSources/UnitTestsMain.cpp UnitTestsSources/Versions.cpp UnitTestsSources/Zip.cpp |
diffstat | 44 files changed, 3832 insertions(+), 2680 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Jun 25 15:37:48 2014 +0200 +++ b/CMakeLists.txt Mon Jun 30 13:36:01 2014 +0200 @@ -84,6 +84,7 @@ Core/HttpServer/MongooseServer.cpp Core/HttpServer/HttpFileSender.cpp Core/HttpServer/FilesystemHttpSender.cpp + Core/RestApi/RestApiHierarchy.cpp Core/RestApi/RestApiPath.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApi.cpp @@ -156,22 +157,23 @@ set(ORTHANC_UNIT_TESTS_SOURCES - UnitTestsSources/DicomMap.cpp - UnitTestsSources/FileStorage.cpp - UnitTestsSources/FromDcmtk.cpp - UnitTestsSources/MemoryCache.cpp - UnitTestsSources/Png.cpp - UnitTestsSources/RestApi.cpp - UnitTestsSources/SQLite.cpp - UnitTestsSources/SQLiteChromium.cpp + UnitTestsSources/DicomMapTests.cpp + UnitTestsSources/FileStorageTests.cpp + UnitTestsSources/FromDcmtkTests.cpp + UnitTestsSources/MemoryCacheTests.cpp + UnitTestsSources/PngTests.cpp + UnitTestsSources/RestApiTests.cpp + UnitTestsSources/SQLiteTests.cpp + UnitTestsSources/SQLiteChromiumTests.cpp UnitTestsSources/ServerIndexTests.cpp - UnitTestsSources/Versions.cpp - UnitTestsSources/Zip.cpp - UnitTestsSources/Lua.cpp - UnitTestsSources/MultiThreading.cpp + UnitTestsSources/VersionsTests.cpp + UnitTestsSources/ZipTests.cpp + UnitTestsSources/LuaTests.cpp + UnitTestsSources/MultiThreadingTests.cpp UnitTestsSources/UnitTestsMain.cpp UnitTestsSources/ImageProcessingTests.cpp - UnitTestsSources/JpegLossless.cpp + UnitTestsSources/JpegLosslessTests.cpp + UnitTestsSources/PluginsTests.cpp )
--- a/Core/DicomFormat/DicomTag.cpp Wed Jun 25 15:37:48 2014 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -116,4 +116,132 @@ return ""; } + + + void DicomTag::GetTagsForModule(std::set<DicomTag>& target, + ResourceType module) + { + // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions + target.clear(); + + switch (module) + { + case ResourceType_Patient: + // This is Table C.7-1 "Patient Module Attributes" (p. 373) + target.insert(DicomTag(0x0010, 0x0010)); // Patient's name + target.insert(DicomTag(0x0010, 0x0020)); // Patient ID + target.insert(DicomTag(0x0010, 0x0030)); // Patient's birth date + target.insert(DicomTag(0x0010, 0x0040)); // Patient's sex + target.insert(DicomTag(0x0008, 0x1120)); // Referenced patient sequence + target.insert(DicomTag(0x0010, 0x0032)); // Patient's birth time + target.insert(DicomTag(0x0010, 0x1000)); // Other patient IDs + target.insert(DicomTag(0x0010, 0x1002)); // Other patient IDs sequence + target.insert(DicomTag(0x0010, 0x1001)); // Other patient names + target.insert(DicomTag(0x0010, 0x2160)); // Ethnic group + target.insert(DicomTag(0x0010, 0x4000)); // Patient comments + target.insert(DicomTag(0x0010, 0x2201)); // Patient species description + target.insert(DicomTag(0x0010, 0x2202)); // Patient species code sequence + target.insert(DicomTag(0x0010, 0x2292)); // Patient breed description + target.insert(DicomTag(0x0010, 0x2293)); // Patient breed code sequence + target.insert(DicomTag(0x0010, 0x2294)); // Breed registration sequence + target.insert(DicomTag(0x0010, 0x2297)); // Responsible person + target.insert(DicomTag(0x0010, 0x2298)); // Responsible person role + target.insert(DicomTag(0x0010, 0x2299)); // Responsible organization + target.insert(DicomTag(0x0012, 0x0062)); // Patient identity removed + target.insert(DicomTag(0x0012, 0x0063)); // De-identification method + target.insert(DicomTag(0x0012, 0x0064)); // De-identification method code sequence + + // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112) + target.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID + target.insert(DicomTag(0x0010, 0x0024)); // Issuer of Patient ID qualifiers sequence + break; + + case ResourceType_Study: + // This is Table C.7-3 "General Study Module Attributes" (p. 378) + target.insert(DicomTag(0x0020, 0x000d)); // Study instance UID + target.insert(DicomTag(0x0008, 0x0020)); // Study date + target.insert(DicomTag(0x0008, 0x0030)); // Study time + target.insert(DicomTag(0x0008, 0x0090)); // Referring physician's name + target.insert(DicomTag(0x0008, 0x0096)); // Referring physician identification sequence + target.insert(DicomTag(0x0020, 0x0010)); // Study ID + target.insert(DicomTag(0x0008, 0x0050)); // Accession number + target.insert(DicomTag(0x0008, 0x0051)); // Issuer of accession number sequence + target.insert(DicomTag(0x0008, 0x1030)); // Study description + target.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of record + target.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of record identification sequence + target.insert(DicomTag(0x0008, 0x1060)); // Name of physician(s) reading study + target.insert(DicomTag(0x0008, 0x1062)); // Physician(s) reading study identification sequence + target.insert(DicomTag(0x0032, 0x1034)); // Requesting service code sequence + target.insert(DicomTag(0x0008, 0x1110)); // Referenced study sequence + target.insert(DicomTag(0x0008, 0x1032)); // Procedure code sequence + target.insert(DicomTag(0x0040, 0x1012)); // Reason for performed procedure code sequence + break; + + case ResourceType_Series: + // This is Table C.7-5 "General Series Module Attributes" (p. 385) + target.insert(DicomTag(0x0008, 0x0060)); // Modality + target.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID + target.insert(DicomTag(0x0020, 0x0011)); // Series Number + target.insert(DicomTag(0x0020, 0x0060)); // Laterality + target.insert(DicomTag(0x0008, 0x0021)); // Series Date + target.insert(DicomTag(0x0008, 0x0031)); // Series Time + target.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians’ Name + target.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence + target.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + target.insert(DicomTag(0x0008, 0x103e)); // Series Description + target.insert(DicomTag(0x0008, 0x103f)); // Series Description Code Sequence + target.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + target.insert(DicomTag(0x0008, 0x1072)); // Operator Identification Sequence + target.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence + target.insert(DicomTag(0x0008, 0x1250)); // Related Series Sequence + target.insert(DicomTag(0x0018, 0x0015)); // Body Part Examined + target.insert(DicomTag(0x0018, 0x5100)); // Patient Position + target.insert(DicomTag(0x0028, 0x0108)); // Smallest Pixel Value in Series + target.insert(DicomTag(0x0029, 0x0109)); // Largest Pixel Value in Series + target.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + target.insert(DicomTag(0x0010, 0x2210)); // Anatomical Orientation Type + + // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES + target.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + target.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + target.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + target.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + target.insert(DicomTag(0x0040, 0x0260)); // Performed Protocol Code Sequence + target.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step + break; + + case ResourceType_Instance: + // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207) + target.insert(DicomTag(0x0008, 0x0016)); // SOP Class UID + target.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID + target.insert(DicomTag(0x0008, 0x0005)); // Specific Character Set + target.insert(DicomTag(0x0008, 0x0012)); // Instance Creation Date + target.insert(DicomTag(0x0008, 0x0013)); // Instance Creation Time + target.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + target.insert(DicomTag(0x0008, 0x001a)); // Related General SOP Class UID + target.insert(DicomTag(0x0008, 0x001b)); // Original Specialized SOP Class UID + target.insert(DicomTag(0x0008, 0x0110)); // Coding Scheme Identification Sequence + target.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + target.insert(DicomTag(0x0018, 0xa001)); // Contributing Equipment Sequence + target.insert(DicomTag(0x0020, 0x0013)); // Instance Number + target.insert(DicomTag(0x0100, 0x0410)); // SOP Instance Status + target.insert(DicomTag(0x0100, 0x0420)); // SOP Authorization DateTime + target.insert(DicomTag(0x0100, 0x0424)); // SOP Authorization Comment + target.insert(DicomTag(0x0100, 0x0426)); // Authorization Equipment Certification Number + target.insert(DicomTag(0x0400, 0x0500)); // Encrypted Attributes Sequence + target.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + target.insert(DicomTag(0x0040, 0xa390)); // HL7 Structured Document Reference Sequence + target.insert(DicomTag(0x0028, 0x0303)); // Longitudinal Temporal Information Modified + + // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216) + target.insert(DicomTag(0x4ffe, 0x0001)); // MAC Parameters sequence + target.insert(DicomTag(0xfffa, 0xfffa)); // Digital signatures sequence + break; + + // TODO IMAGE MODULE? + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } }
--- a/Core/DicomFormat/DicomTag.h Wed Jun 25 15:37:48 2014 +0200 +++ b/Core/DicomFormat/DicomTag.h Mon Jun 30 13:36:01 2014 +0200 @@ -33,8 +33,10 @@ #pragma once #include <string> +#include <set> #include <stdint.h> +#include "../Enumerations.h" namespace Orthanc { @@ -81,6 +83,9 @@ std::string Format() const; friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); + + static void GetTagsForModule(std::set<DicomTag>& target, + ResourceType module); }; // Aliases for the most useful tags
--- a/Core/Enumerations.h Wed Jun 25 15:37:48 2014 +0200 +++ b/Core/Enumerations.h Mon Jun 30 13:36:01 2014 +0200 @@ -231,6 +231,7 @@ enum Encoding { + Encoding_Ascii, Encoding_Utf8, Encoding_Latin1 };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiHierarchy.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,412 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "RestApiHierarchy.h" + +#include <cassert> + +namespace Orthanc +{ + bool RestApiHierarchy::Handlers::IsEmpty() const + { + return (getHandlers_.empty() && + postHandlers_.empty() && + putHandlers_.empty() && + deleteHandlers_.empty()); + } + + + RestApiHierarchy& RestApiHierarchy::AddChild(Children& children, + const std::string& name) + { + Children::iterator it = children.find(name); + + if (it == children.end()) + { + // Create new child + RestApiHierarchy *child = new RestApiHierarchy; + children[name] = child; + return *child; + } + else + { + return *it->second; + } + } + + + void RestApiHierarchy::DeleteChildren(Children& children) + { + for (Children::iterator it = children.begin(); + it != children.end(); it++) + { + delete it->second; + } + } + + + template <typename Handler> + void RestApiHierarchy::RegisterInternal(const RestApiPath& path, + Handler handler, + size_t level) + { + if (path.GetLevelCount() == level) + { + if (path.IsUniversalTrailing()) + { + universalHandlers_.Register(handler); + } + else + { + handlers_.Register(handler); + } + } + else + { + RestApiHierarchy* child; + if (path.IsWildcardLevel(level)) + { + child = &AddChild(wildcardChildren_, path.GetWildcardName(level)); + } + else + { + child = &AddChild(children_, path.GetLevelName(level)); + } + + child->RegisterInternal(path, handler, level + 1); + } + } + + + bool RestApiHierarchy::LookupHandler(RestApiPath::Components& components, + const UriComponents& uri, + ResourceCallback callback, + size_t level, + void* call) + { + assert(uri.size() >= level); + UriComponents trailing; + + // Look for an exact match on the resource of interest + if (uri.size() == level) + { + if (!handlers_.IsEmpty() && + callback(handlers_, uri, components, trailing, call)) + { + return true; + } + } + + + // Try and go down in the hierarchy, using an exact match for the child + Children::const_iterator child = children_.find(uri[level]); + if (child != children_.end()) + { + if (child->second->LookupHandler(components, uri, callback, level + 1, call)) + { + return true; + } + } + + + // Try and go down in the hierarchy, using wildcard rules for children + for (child = wildcardChildren_.begin(); + child != wildcardChildren_.end(); child++) + { + RestApiPath::Components subComponents = components; + subComponents[child->first] = uri[level]; + + if (child->second->LookupHandler(components, uri, callback, level + 1, call)) + { + return true; + } + } + + + // As a last resort, call the universal handlers, if any + if (!universalHandlers_.IsEmpty()) + { + trailing.resize(uri.size() - level); + size_t pos = 0; + for (size_t i = level; i < uri.size(); i++, pos++) + { + trailing[pos] = uri[i]; + } + + assert(pos == trailing.size()); + + if (callback(universalHandlers_, uri, components, trailing, call)) + { + return true; + } + } + + return false; + } + + + bool RestApiHierarchy::GetDirectory(Json::Value& result, + const UriComponents& uri, + size_t level) + { + if (uri.size() == level) + { + if (!handlers_.HasGet() && + universalHandlers_.IsEmpty() && + wildcardChildren_.size() == 0) + { + result = Json::arrayValue; + + for (Children::const_iterator it = children_.begin(); + it != children_.end(); it++) + { + result.append(it->first); + } + + return true; + } + else + { + return false; + } + } + + Children::const_iterator child = children_.find(uri[level]); + if (child != children_.end()) + { + if (child->second->GetDirectory(result, uri, level + 1)) + { + return true; + } + } + + for (child = wildcardChildren_.begin(); + child != wildcardChildren_.end(); child++) + { + if (child->second->GetDirectory(result, uri, level + 1)) + { + return true; + } + } + + return false; + } + + + bool RestApiHierarchy::GetCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call) + { + for (Handlers::GetHandlers::iterator + it = handlers.getHandlers_.begin(); + it != handlers.getHandlers_.end(); it++) + { + // TODO RETURN BOOL + + (*it) (*reinterpret_cast<RestApi::GetCall*>(call)); + return true; + } + + return false; + } + + + bool RestApiHierarchy::PostCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call) + { + for (Handlers::PostHandlers::iterator + it = handlers.postHandlers_.begin(); + it != handlers.postHandlers_.end(); it++) + { + // TODO RETURN BOOL + + (*it) (*reinterpret_cast<RestApi::PostCall*>(call)); + return true; + } + + return false; + } + + + bool RestApiHierarchy::PutCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call) + { + for (Handlers::PutHandlers::iterator + it = handlers.putHandlers_.begin(); + it != handlers.putHandlers_.end(); it++) + { + // TODO RETURN BOOL + + (*it) (*reinterpret_cast<RestApi::PutCall*>(call)); + return true; + } + + return false; + } + + + bool RestApiHierarchy::DeleteCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call) + { + for (Handlers::DeleteHandlers::iterator + it = handlers.deleteHandlers_.begin(); + it != handlers.deleteHandlers_.end(); it++) + { + // TODO RETURN BOOL + + (*it) (*reinterpret_cast<RestApi::DeleteCall*>(call)); + return true; + } + + return false; + } + + + RestApiHierarchy::~RestApiHierarchy() + { + DeleteChildren(children_); + DeleteChildren(wildcardChildren_); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApi::GetHandler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApi::PutHandler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApi::PostHandler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApi::DeleteHandler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::CreateSiteMap(Json::Value& target) const + { + if (children_.size() == 0) + { + std::string s = " "; + if (handlers_.getHandlers_.size() != 0) + { + s += "GET "; + } + + if (handlers_.postHandlers_.size() != 0) + { + s += "POST "; + } + + if (handlers_.putHandlers_.size() != 0) + { + s += "PUT "; + } + + if (handlers_.deleteHandlers_.size() != 0) + { + s += "DELETE "; + } + + target = s; + } + else + { + target = Json::objectValue; + + for (Children::const_iterator it = children_.begin(); + it != children_.end(); it++) + { + it->second->CreateSiteMap(target[it->first]); + } + } + + /*for (Children::const_iterator it = wildcardChildren_.begin(); + it != wildcardChildren_.end(); it++) + { + it->second->CreateSiteMap(target["* (" + it->first + ")"]); + }*/ + } + + bool RestApiHierarchy::Handle(RestApi::GetCall& call, + const UriComponents& uri) + { + RestApiPath::Components components; + return LookupHandler(components, uri, GetCallback, 0, &call); + } + + bool RestApiHierarchy::Handle(RestApi::PutCall& call, + const UriComponents& uri) + { + RestApiPath::Components components; + return LookupHandler(components, uri, PutCallback, 0, &call); + } + + bool RestApiHierarchy::Handle(RestApi::PostCall& call, + const UriComponents& uri) + { + RestApiPath::Components components; + return LookupHandler(components, uri, PostCallback, 0, &call); + } + + bool RestApiHierarchy::Handle(RestApi::DeleteCall& call, + const UriComponents& uri) + { + RestApiPath::Components components; + return LookupHandler(components, uri, DeleteCallback, 0, &call); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiHierarchy.h Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,178 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "RestApi.h" + +#include <list> + +namespace Orthanc +{ + class RestApiHierarchy + { + private: + struct Handlers + { + typedef std::list<RestApi::GetHandler> GetHandlers; + typedef std::list<RestApi::PostHandler> PostHandlers; + typedef std::list<RestApi::PutHandler> PutHandlers; + typedef std::list<RestApi::DeleteHandler> DeleteHandlers; + + GetHandlers getHandlers_; + PostHandlers postHandlers_; + PutHandlers putHandlers_; + DeleteHandlers deleteHandlers_; + + bool HasGet() const + { + return getHandlers_.size() > 0; + } + + void Register(RestApi::GetHandler handler) + { + getHandlers_.push_back(handler); + } + + void Register(RestApi::PutHandler handler) + { + putHandlers_.push_back(handler); + } + + void Register(RestApi::PostHandler handler) + { + postHandlers_.push_back(handler); + } + + void Register(RestApi::DeleteHandler handler) + { + deleteHandlers_.push_back(handler); + } + + bool IsEmpty() const; + }; + + + typedef std::map<std::string, RestApiHierarchy*> Children; + typedef bool (*ResourceCallback) (Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call); + + Handlers handlers_; + Children children_; + Children wildcardChildren_; + Handlers universalHandlers_; + + + static RestApiHierarchy& AddChild(Children& children, + const std::string& name); + + static void DeleteChildren(Children& children); + + template <typename Handler> + void RegisterInternal(const RestApiPath& path, + Handler handler, + size_t level); + + bool LookupHandler(RestApiPath::Components& components, + const UriComponents& uri, + ResourceCallback callback, + size_t level, + void* call); + + bool GetDirectory(Json::Value& result, + const UriComponents& uri, + size_t level); + + static bool GetCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call); + + static bool PostCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call); + + static bool PutCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call); + + static bool DeleteCallback(Handlers& handlers, + const UriComponents& uri, + const RestApiPath::Components& components, + const UriComponents& trailing, + void* call); + + + public: + ~RestApiHierarchy(); + + void Register(const std::string& uri, + RestApi::GetHandler handler); + + void Register(const std::string& uri, + RestApi::PutHandler handler); + + void Register(const std::string& uri, + RestApi::PostHandler handler); + + void Register(const std::string& uri, + RestApi::DeleteHandler handler); + + void CreateSiteMap(Json::Value& target) const; + + bool GetDirectory(Json::Value& result, + const UriComponents& uri) + { + return GetDirectory(result, uri, 0); + } + + bool Handle(RestApi::GetCall& call, + const UriComponents& uri); + + bool Handle(RestApi::PutCall& call, + const UriComponents& uri); + + bool Handle(RestApi::PostCall& call, + const UriComponents& uri); + + bool Handle(RestApi::DeleteCall& call, + const UriComponents& uri); + }; +}
--- a/Core/RestApi/RestApiPath.cpp Wed Jun 25 15:37:48 2014 +0200 +++ b/Core/RestApi/RestApiPath.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -33,6 +33,8 @@ #include "../PrecompiledHeaders.h" #include "RestApiPath.h" +#include "../OrthancException.h" + #include <cassert> namespace Orthanc @@ -89,6 +91,8 @@ UriComponents& trailing, const UriComponents& uri) const { + assert(uri_.size() == components_.size()); + if (uri.size() < uri_.size()) { return false; @@ -135,4 +139,42 @@ UriComponents trailing; return Match(components, trailing, uri); } + + + bool RestApiPath::IsWildcardLevel(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (level >= uri_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + return uri_[level].length() == 0; + } + + const std::string& RestApiPath::GetWildcardName(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (!IsWildcardLevel(level)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + return components_[level]; + } + + const std::string& RestApiPath::GetLevelName(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (IsWildcardLevel(level)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + return uri_[level]; + } } +
--- a/Core/RestApi/RestApiPath.h Wed Jun 25 15:37:48 2014 +0200 +++ b/Core/RestApi/RestApiPath.h Mon Jun 30 13:36:01 2014 +0200 @@ -59,5 +59,22 @@ const UriComponents& uri) const; bool Match(const UriComponents& uri) const; + + size_t GetLevelCount() const + { + return uri_.size(); + } + + bool IsWildcardLevel(size_t level) const; + + bool IsUniversalTrailing() const + { + return hasTrailing_; + } + + const std::string& GetWildcardName(size_t level) const; + + const std::string& GetLevelName(size_t level) const; + }; }
--- a/Core/Toolbox.cpp Wed Jun 25 15:37:48 2014 +0200 +++ b/Core/Toolbox.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -284,6 +284,28 @@ } + void Toolbox::TruncateUri(UriComponents& target, + const UriComponents& source, + size_t fromLevel) + { + target.clear(); + + if (source.size() > fromLevel) + { + target.resize(source.size() - fromLevel); + + size_t j = 0; + for (size_t i = fromLevel; i < source.size(); i++, j++) + { + target[j] = source[i]; + } + + assert(j == target.size()); + } + } + + + bool Toolbox::IsChildUri(const UriComponents& baseUri, const UriComponents& testedUri) { @@ -505,12 +527,15 @@ // Already in UTF-8: No conversion is required return source; + case Encoding_Ascii: + return ConvertToAscii(source);; + case Encoding_Latin1: encoding = "ISO-8859-1"; break; default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_NotImplemented); } try
--- a/Core/Toolbox.h Wed Jun 25 15:37:48 2014 +0200 +++ b/Core/Toolbox.h Mon Jun 30 13:36:01 2014 +0200 @@ -73,6 +73,10 @@ void SplitUriComponents(UriComponents& components, const std::string& uri); + void TruncateUri(UriComponents& target, + const UriComponents& source, + size_t fromLevel); + bool IsChildUri(const UriComponents& baseUri, const UriComponents& testedUri);
--- a/DarwinCompilation.txt Wed Jun 25 15:37:48 2014 +0200 +++ b/DarwinCompilation.txt Mon Jun 30 13:36:01 2014 +0200 @@ -28,7 +28,7 @@ ---------------------------- # cd ~/OrthancBuild -# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON .. +# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON ~/Orthanc NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to your version of XCode.
--- a/NEWS Wed Jun 25 15:37:48 2014 +0200 +++ b/NEWS Mon Jun 30 13:36:01 2014 +0200 @@ -3,6 +3,8 @@ * Introduction of the Orthanc Plugin SDK * Official support of OS X (Darwin) +* Extraction of tags for the patient/study/series/instance DICOM modules +* Extraction of the tags shared by all the instances of a patient/study/series * Options to limit the number of results for an incoming C-FIND query * Support of kFreeBSD
--- a/OrthancServer/FromDcmtkBridge.cpp Wed Jun 25 15:37:48 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -61,6 +61,7 @@ #include <dcmtk/dcmdata/dcistrmb.h> #include <dcmtk/dcmdata/dcuid.h> #include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> #include <dcmtk/dcmdata/dcvrae.h> #include <dcmtk/dcmdata/dcvras.h> @@ -115,8 +116,44 @@ GetCharValue(c[3])); } + + Encoding FromDcmtkBridge::DetectEncoding(DcmDataset& dataset) + { + // By default, assume UTF-8 encoding (as in dcm2xml.cc) + Encoding encoding = Encoding_Utf8; + + OFString tmp; + if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) + { + std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); + + // TODO Add more encodings + + if (characterSet == "ISO_IR 6" || + characterSet == "ISO_IR 192") + { + encoding = Encoding_Utf8; + } + else if (characterSet == "ISO_IR 100") + { + encoding = Encoding_Latin1; + } + else if (!characterSet.empty()) + { + LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet; + // Fallback to ASCII (remove all special characters) + encoding = Encoding_Ascii; + } + } + + return encoding; + } + + void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) { + Encoding encoding = DetectEncoding(dataset); + target.Clear(); for (unsigned long i = 0; i < dataset.card(); i++) { @@ -125,7 +162,7 @@ { target.SetValue(element->getTag().getGTag(), element->getTag().getETag(), - ConvertLeafElement(*element)); + ConvertLeafElement(*element, encoding)); } } } @@ -137,7 +174,8 @@ } - DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element) + DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, + Encoding encoding) { if (!element.isLeaf()) { @@ -151,7 +189,7 @@ c != NULL) { std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, Encoding_Latin1); // TODO Parameter? + std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); return new DicomString(utf8); } else @@ -313,25 +351,28 @@ static void StoreElement(Json::Value& target, DcmElement& element, - unsigned int maxStringLength); + unsigned int maxStringLength, + Encoding encoding); static void StoreItem(Json::Value& target, DcmItem& item, - unsigned int maxStringLength) + unsigned int maxStringLength, + Encoding encoding) { target = Json::Value(Json::objectValue); for (unsigned long i = 0; i < item.card(); i++) { DcmElement* element = item.getElement(i); - StoreElement(target, *element, maxStringLength); + StoreElement(target, *element, maxStringLength, encoding); } } static void StoreElement(Json::Value& target, DcmElement& element, - unsigned int maxStringLength) + unsigned int maxStringLength, + Encoding encoding) { assert(target.type() == Json::objectValue); @@ -356,7 +397,7 @@ value["PrivateCreator"] = tagbis.getPrivateCreator(); } - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element)); + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, encoding)); if (v->IsNull()) { value["Type"] = "Null"; @@ -393,7 +434,7 @@ { DcmItem* child = sequence.getItem(i); Json::Value& v = children.append(Json::objectValue); - StoreItem(v, *child, maxStringLength); + StoreItem(v, *child, maxStringLength, encoding); } target[formattedTag]["Name"] = tagName; @@ -407,7 +448,7 @@ DcmDataset& dataset, unsigned int maxStringLength) { - StoreItem(root, dataset, maxStringLength); + StoreItem(root, dataset, maxStringLength, DetectEncoding(dataset)); }
--- a/OrthancServer/FromDcmtkBridge.h Wed Jun 25 15:37:48 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Mon Jun 30 13:36:01 2014 +0200 @@ -44,11 +44,14 @@ class FromDcmtkBridge { public: + static Encoding DetectEncoding(DcmDataset& dataset); + static void Convert(DicomMap& target, DcmDataset& dataset); static DicomTag GetTag(const DcmElement& element); - static DicomValue* ConvertLeafElement(DcmElement& element); + static DicomValue* ConvertLeafElement(DcmElement& element, + Encoding encoding); static void ToJson(Json::Value& target, DcmDataset& dataset,
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Jun 25 15:37:48 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -153,6 +153,21 @@ } } + + static void GetInstanceTagsBis(RestApi::GetCall& call) + { + bool simplify = call.HasArgument("simplify"); + + if (simplify) + { + GetInstanceTags<true>(call); + } + else + { + GetInstanceTags<false>(call); + } + } + static void ListFrames(RestApi::GetCall& call) { @@ -591,6 +606,160 @@ + static bool ExtractSharedTags(Json::Value& shared, + ServerContext& context, + const std::string& publicId) + { + // Retrieve all the instances of this patient/study/series + typedef std::list<std::string> Instances; + Instances instances; + context.GetIndex().GetChildInstances(instances, publicId); // (*) + + // Loop over the instances + bool isFirst = true; + shared = Json::objectValue; + + for (Instances::const_iterator it = instances.begin(); + it != instances.end(); it++) + { + // Get the tags of the current instance, in the simplified format + Json::Value tags; + + try + { + context.ReadJson(tags, *it); + } + catch (OrthancException&) + { + // Race condition: This instance has been removed since + // (*). Ignore this instance. + continue; + } + + if (tags.type() != Json::objectValue) + { + return false; // Error + } + + // Only keep the tags that are mapped to a string + Json::Value::Members members = tags.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& tag = tags[members[i]]; + if (tag.type() != Json::objectValue || + tag["Type"].type() != Json::stringValue || + tag["Type"].asString() != "String") + { + tags.removeMember(members[i]); + } + } + + if (isFirst) + { + // This is the first instance, keep its tags as such + shared = tags; + isFirst = false; + } + else + { + // Loop over all the members of the shared tags extracted so + // far. If the value of one of these tags does not match its + // value in the current instance, remove it. + members = shared.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + if (!tags.isMember(members[i]) || + tags[members[i]]["Value"].asString() != shared[members[i]]["Value"].asString()) + { + shared.removeMember(members[i]); + } + } + } + } + + return true; + } + + + static void GetSharedTags(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + std::string publicId = call.GetUriComponent("id", ""); + bool simplify = call.HasArgument("simplify"); + + Json::Value sharedTags; + if (ExtractSharedTags(sharedTags, context, publicId)) + { + // Success: Send the value of the shared tags + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, sharedTags); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(sharedTags); + } + } + } + + + template <enum ResourceType resourceType> + static void GetModule(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + std::string publicId = call.GetUriComponent("id", ""); + bool simplify = call.HasArgument("simplify"); + + typedef std::set<DicomTag> Module; + Module module; + DicomTag::GetTagsForModule(module, resourceType); + + Json::Value tags; + + if (resourceType != ResourceType_Instance) + { + // Retrieve all the instances of this patient/study/series + typedef std::list<std::string> Instances; + Instances instances; + context.GetIndex().GetChildInstances(instances, publicId); + + if (instances.empty()) + { + return; // Error: No instance (should never happen) + } + + // Select one child instance + publicId = instances.front(); + } + + context.ReadJson(tags, publicId); + + // Filter the tags of the instance according to the module + Json::Value result = Json::objectValue; + for (Module::const_iterator it = module.begin(); it != module.end(); it++) + { + std::string s = it->Format(); + if (tags.isMember(s)) + { + result[s] = tags[s]; + } + } + + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, result); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(result); + } + } + + void OrthancRestApi::RegisterResources() { Register("/instances", ListResources<ResourceType_Instance>); @@ -612,9 +781,18 @@ Register("/studies/{id}/statistics", GetResourceStatistics); Register("/series/{id}/statistics", GetResourceStatistics); + Register("/patients/{id}/shared-tags", GetSharedTags); + Register("/series/{id}/shared-tags", GetSharedTags); + Register("/studies/{id}/shared-tags", GetSharedTags); + + Register("/instances/{id}/module", GetModule<ResourceType_Instance>); + Register("/patients/{id}/module", GetModule<ResourceType_Patient>); + Register("/series/{id}/module", GetModule<ResourceType_Series>); + Register("/studies/{id}/module", GetModule<ResourceType_Study>); + Register("/instances/{id}/file", GetInstanceFile); Register("/instances/{id}/export", ExportInstanceFile); - Register("/instances/{id}/tags", GetInstanceTags<false>); + Register("/instances/{id}/tags", GetInstanceTagsBis); Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); Register("/instances/{id}/frames", ListFrames);
--- a/OrthancServer/ParsedDicomFile.cpp Wed Jun 25 15:37:48 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -146,6 +146,7 @@ struct ParsedDicomFile::PImpl { std::auto_ptr<DcmFileFormat> file_; + Encoding encoding_; }; @@ -170,6 +171,8 @@ } pimpl_->file_->loadAllDataIntoMemory(); pimpl_->file_->transferEnd(); + + pimpl_->encoding_ = FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset()); } @@ -872,7 +875,7 @@ return false; } - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element)); + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_)); if (v.get() == NULL) { @@ -887,7 +890,6 @@ } - DicomInstanceHasher ParsedDicomFile::GetHasher() { std::string patientId, studyUid, seriesUid, instanceUid; @@ -904,98 +906,6 @@ } - static void StoreElement(Json::Value& target, - DcmElement& element, - unsigned int maxStringLength); - - static void StoreItem(Json::Value& target, - DcmItem& item, - unsigned int maxStringLength) - { - target = Json::Value(Json::objectValue); - - for (unsigned long i = 0; i < item.card(); i++) - { - DcmElement* element = item.getElement(i); - StoreElement(target, *element, maxStringLength); - } - } - - - static void StoreElement(Json::Value& target, - DcmElement& element, - unsigned int maxStringLength) - { - assert(target.type() == Json::objectValue); - - DicomTag tag(FromDcmtkBridge::GetTag(element)); - const std::string formattedTag = tag.Format(); - -#if 0 - const std::string tagName = FromDcmtkBridge::GetName(tag); -#else - // This version of the code gives access to the name of the private tags - DcmTag tagbis(element.getTag()); - const std::string tagName(tagbis.getTagName()); -#endif - - if (element.isLeaf()) - { - Json::Value value(Json::objectValue); - value["Name"] = tagName; - - if (tagbis.getPrivateCreator() != NULL) - { - value["PrivateCreator"] = tagbis.getPrivateCreator(); - } - - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element)); - if (v->IsNull()) - { - value["Type"] = "Null"; - value["Value"] = Json::nullValue; - } - else - { - std::string s = v->AsString(); - if (maxStringLength == 0 || - s.size() <= maxStringLength) - { - value["Type"] = "String"; - value["Value"] = s; - } - else - { - value["Type"] = "TooLong"; - value["Value"] = Json::nullValue; - } - } - - target[formattedTag] = value; - } - else - { - Json::Value children(Json::arrayValue); - - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); - - for (unsigned long i = 0; i < sequence.card(); i++) - { - DcmItem* child = sequence.getItem(i); - Json::Value& v = children.append(Json::objectValue); - StoreItem(v, *child, maxStringLength); - } - - target[formattedTag]["Name"] = tagName; - target[formattedTag]["Type"] = "Sequence"; - target[formattedTag]["Value"] = children; - } - } - - template <typename T> static void ExtractPngImageTruncate(std::string& result, DicomIntegerPixelAccessor& accessor, @@ -1044,6 +954,7 @@ ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl) { pimpl_->file_.reset(new DcmFileFormat); + pimpl_->encoding_ = Encoding_Ascii; Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); @@ -1073,6 +984,8 @@ pimpl_(new PImpl) { pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone())); + + pimpl_->encoding_ = other.pimpl_->encoding_; } @@ -1279,4 +1192,9 @@ writer.WriteToMemory(result, accessor); } + + Encoding ParsedDicomFile::GetEncoding() const + { + return pimpl_->encoding_; + } }
--- a/OrthancServer/ParsedDicomFile.h Wed Jun 25 15:37:48 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.h Mon Jun 30 13:36:01 2014 +0200 @@ -104,6 +104,8 @@ void ExtractPngImage(std::string& result, unsigned int frame, ImageExtractionMode mode); + + Encoding GetEncoding() const; }; }
--- a/UnitTestsSources/DicomMap.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Uuid.h" -#include "../Core/OrthancException.h" -#include "../Core/DicomFormat/DicomMap.h" -#include "../Core/DicomFormat/DicomNullValue.h" - -#include <memory> - -using namespace Orthanc; - -TEST(DicomMap, MainTags) -{ - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); - ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); - - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID)); - - std::set<DicomTag> s; - DicomMap::GetMainDicomTags(s); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Patient); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Study); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Series); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Instance); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); -} - - -TEST(DicomMap, Tags) -{ - DicomMap m; - ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME)); - ASSERT_FALSE(m.HasTag(0x0010, 0x0010)); - m.SetValue(0x0010, 0x0010, "PatientName"); - ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME)); - ASSERT_TRUE(m.HasTag(0x0010, 0x0010)); - - ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID)); - m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); - ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); - m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); - ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString()); - - m.Remove(DICOM_TAG_PATIENT_ID); - ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException); - - std::auto_ptr<DicomMap> mm(m.Clone()); - ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); - - m.SetValue(DICOM_TAG_PATIENT_ID, "Hello"); - ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException); - mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID); - ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString()); - - DicomNullValue v; - ASSERT_TRUE(v.IsNull()); -} - - -TEST(DicomMap, FindTemplates) -{ - DicomMap m; - - DicomMap::SetupFindPatientTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID)); - - DicomMap::SetupFindStudyTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(m.HasTag(DICOM_TAG_ACCESSION_NUMBER)); - - DicomMap::SetupFindSeriesTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)); - - DicomMap::SetupFindInstanceTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID)); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/DicomMapTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,189 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Uuid.h" +#include "../Core/OrthancException.h" +#include "../Core/DicomFormat/DicomMap.h" +#include "../Core/DicomFormat/DicomNullValue.h" +#include "../OrthancServer/FromDcmtkBridge.h" + +#include <memory> + +using namespace Orthanc; + +TEST(DicomMap, MainTags) +{ + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); + ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); + + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID)); + + std::set<DicomTag> s; + DicomMap::GetMainDicomTags(s); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Patient); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Study); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Series); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Instance); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); +} + + +TEST(DicomMap, Tags) +{ + DicomMap m; + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME)); + ASSERT_FALSE(m.HasTag(0x0010, 0x0010)); + m.SetValue(0x0010, 0x0010, "PatientName"); + ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.HasTag(0x0010, 0x0010)); + + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID)); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); + ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); + ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString()); + + m.Remove(DICOM_TAG_PATIENT_ID); + ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException); + + std::auto_ptr<DicomMap> mm(m.Clone()); + ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); + + m.SetValue(DICOM_TAG_PATIENT_ID, "Hello"); + ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException); + mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID); + ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString()); + + DicomNullValue v; + ASSERT_TRUE(v.IsNull()); +} + + +TEST(DicomMap, FindTemplates) +{ + DicomMap m; + + DicomMap::SetupFindPatientTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID)); + + DicomMap::SetupFindStudyTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(m.HasTag(DICOM_TAG_ACCESSION_NUMBER)); + + DicomMap::SetupFindSeriesTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)); + + DicomMap::SetupFindInstanceTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID)); +} + + + + +static void TestModule(ResourceType level) +{ + std::set<DicomTag> module, main; + DicomTag::GetTagsForModule(module, level); + DicomMap::GetMainDicomTags(main, level); + + // The main dicom tags are a subset of the module + for (std::set<DicomTag>::const_iterator it = main.begin(); it != main.end(); it++) + { + bool ok = module.find(*it) != module.end(); + + // Exceptions for the Series level + /*if ((// + *it == DicomTag(0x, 0x) && + level == ResourceType_Series)) + { + ok = true; + }*/ + + // Exceptions for the Instance level + if ((/* Accession number, from Image module */ + *it == DicomTag(0x0020, 0x0012) && + level == ResourceType_Instance) || + (/* Image Index, from PET Image module */ + *it == DicomTag(0x0054, 0x1330) && + level == ResourceType_Instance) || + (/* Temporal Position Identifier, from MR Image module */ + *it == DicomTag(0x0020, 0x0100) && + level == ResourceType_Instance) || + (/* Number of Frames, from Multi-frame module attributes, related to Image IOD */ + *it == DicomTag(0x0028, 0x0008) && + level == ResourceType_Instance )) + { + ok = true; + } + + if (!ok) + { + std::cout << it->Format() << ": " << FromDcmtkBridge::GetName(*it) + << " not expected at level " << EnumerationToString(level) << std::endl; + } + + EXPECT_TRUE(ok); + } +} + + +TEST(DicomMap, Modules) +{ + TestModule(ResourceType_Patient); + TestModule(ResourceType_Study); + //TestModule(ResourceType_Series); // TODO + TestModule(ResourceType_Instance); +}
--- a/UnitTestsSources/FileStorage.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,228 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include <ctype.h> -#include <glog/logging.h> - -#include "../Core/FileStorage/FileStorage.h" -#include "../OrthancServer/ServerIndex.h" -#include "../Core/Toolbox.h" -#include "../Core/OrthancException.h" -#include "../Core/Uuid.h" -#include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/HttpServer/BufferHttpSender.h" -#include "../Core/FileStorage/FileStorageAccessor.h" -#include "../Core/FileStorage/CompressedFileStorageAccessor.h" - -using namespace Orthanc; - - -static void StringToVector(std::vector<uint8_t>& v, - const std::string& s) -{ - v.resize(s.size()); - for (size_t i = 0; i < s.size(); i++) - v[i] = s[i]; -} - - -TEST(FileStorage, Basic) -{ - FileStorage s("UnitTestsStorage"); - - std::string data = Toolbox::GenerateUuid(); - std::string uid = s.Create(data); - std::string d; - s.ReadFile(d, uid); - ASSERT_EQ(d.size(), data.size()); - ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); - ASSERT_EQ(s.GetCompressedSize(uid), data.size()); -} - -TEST(FileStorage, Basic2) -{ - FileStorage s("UnitTestsStorage"); - - std::vector<uint8_t> data; - StringToVector(data, Toolbox::GenerateUuid()); - std::string uid = s.Create(data); - std::string d; - s.ReadFile(d, uid); - ASSERT_EQ(d.size(), data.size()); - ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); - ASSERT_EQ(s.GetCompressedSize(uid), data.size()); -} - -TEST(FileStorage, EndToEnd) -{ - FileStorage s("UnitTestsStorage"); - s.Clear(); - - std::list<std::string> u; - for (unsigned int i = 0; i < 10; i++) - { - u.push_back(s.Create(Toolbox::GenerateUuid())); - } - - std::set<std::string> ss; - s.ListAllFiles(ss); - ASSERT_EQ(10u, ss.size()); - - unsigned int c = 0; - for (std::list<std::string>::iterator - i = u.begin(); i != u.end(); i++, c++) - { - ASSERT_TRUE(ss.find(*i) != ss.end()); - if (c < 5) - s.Remove(*i); - } - - s.ListAllFiles(ss); - ASSERT_EQ(5u, ss.size()); - - s.Clear(); - s.ListAllFiles(ss); - ASSERT_EQ(0u, ss.size()); -} - - -TEST(FileStorageAccessor, Simple) -{ - FileStorage s("UnitTestsStorage"); - FileStorageAccessor accessor(s); - - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, NoCompression) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_None); - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, NoCompression2) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_None); - std::vector<uint8_t> data; - StringToVector(data, "Hello world"); - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size())); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, Compression) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, Mix) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - std::string r; - std::string compressedData = "Hello"; - std::string uncompressedData = "HelloWorld"; - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom); - - accessor.SetCompressionForNextOperations(CompressionType_None); - FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom); - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - accessor.Read(r, compressedInfo.GetUuid()); - ASSERT_EQ(compressedData, r); - - accessor.SetCompressionForNextOperations(CompressionType_None); - accessor.Read(r, compressedInfo.GetUuid()); - ASSERT_NE(compressedData, r); - - /* - // This test is too slow on Windows - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException); - */ -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/FileStorageTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,228 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include <ctype.h> +#include <glog/logging.h> + +#include "../Core/FileStorage/FileStorage.h" +#include "../OrthancServer/ServerIndex.h" +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" +#include "../Core/Uuid.h" +#include "../Core/HttpServer/FilesystemHttpSender.h" +#include "../Core/HttpServer/BufferHttpSender.h" +#include "../Core/FileStorage/FileStorageAccessor.h" +#include "../Core/FileStorage/CompressedFileStorageAccessor.h" + +using namespace Orthanc; + + +static void StringToVector(std::vector<uint8_t>& v, + const std::string& s) +{ + v.resize(s.size()); + for (size_t i = 0; i < s.size(); i++) + v[i] = s[i]; +} + + +TEST(FileStorage, Basic) +{ + FileStorage s("UnitTestsStorage"); + + std::string data = Toolbox::GenerateUuid(); + std::string uid = s.Create(data); + std::string d; + s.ReadFile(d, uid); + ASSERT_EQ(d.size(), data.size()); + ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); + ASSERT_EQ(s.GetCompressedSize(uid), data.size()); +} + +TEST(FileStorage, Basic2) +{ + FileStorage s("UnitTestsStorage"); + + std::vector<uint8_t> data; + StringToVector(data, Toolbox::GenerateUuid()); + std::string uid = s.Create(data); + std::string d; + s.ReadFile(d, uid); + ASSERT_EQ(d.size(), data.size()); + ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); + ASSERT_EQ(s.GetCompressedSize(uid), data.size()); +} + +TEST(FileStorage, EndToEnd) +{ + FileStorage s("UnitTestsStorage"); + s.Clear(); + + std::list<std::string> u; + for (unsigned int i = 0; i < 10; i++) + { + u.push_back(s.Create(Toolbox::GenerateUuid())); + } + + std::set<std::string> ss; + s.ListAllFiles(ss); + ASSERT_EQ(10u, ss.size()); + + unsigned int c = 0; + for (std::list<std::string>::iterator + i = u.begin(); i != u.end(); i++, c++) + { + ASSERT_TRUE(ss.find(*i) != ss.end()); + if (c < 5) + s.Remove(*i); + } + + s.ListAllFiles(ss); + ASSERT_EQ(5u, ss.size()); + + s.Clear(); + s.ListAllFiles(ss); + ASSERT_EQ(0u, ss.size()); +} + + +TEST(FileStorageAccessor, Simple) +{ + FileStorage s("UnitTestsStorage"); + FileStorageAccessor accessor(s); + + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, NoCompression) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_None); + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, NoCompression2) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_None); + std::vector<uint8_t> data; + StringToVector(data, "Hello world"); + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size())); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, Compression) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, Mix) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + std::string r; + std::string compressedData = "Hello"; + std::string uncompressedData = "HelloWorld"; + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom); + + accessor.SetCompressionForNextOperations(CompressionType_None); + FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom); + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + accessor.Read(r, compressedInfo.GetUuid()); + ASSERT_EQ(compressedData, r); + + accessor.SetCompressionForNextOperations(CompressionType_None); + accessor.Read(r, compressedInfo.GetUuid()); + ASSERT_NE(compressedData, r); + + /* + // This test is too slow on Windows + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException); + */ +}
--- a/UnitTestsSources/FromDcmtk.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../OrthancServer/FromDcmtkBridge.h" -#include "../OrthancServer/OrthancInitialization.h" -#include "../OrthancServer/DicomModification.h" -#include "../Core/OrthancException.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/PngReader.h" -#include "../Core/ImageFormats/PngWriter.h" - -using namespace Orthanc; - -TEST(DicomFormat, Tag) -{ - ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); - - DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); - ASSERT_EQ(0x0008, t.GetGroup()); - ASSERT_EQ(0x103E, t.GetElement()); - - t = FromDcmtkBridge::ParseTag("0020-e040"); - ASSERT_EQ(0x0020, t.GetGroup()); - ASSERT_EQ(0xe040, t.GetElement()); - - // Test ==() and !=() operators - ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); - ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); -} - - -TEST(DicomModification, Basic) -{ - DicomModification m; - m.SetupAnonymization(); - //m.SetLevel(DicomRootLevel_Study); - //m.Replace(DICOM_TAG_PATIENT_ID, "coucou"); - //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); - - ParsedDicomFile o; - o.SaveToFile("UnitTestsResults/anon.dcm"); - - for (int i = 0; i < 10; i++) - { - char b[1024]; - sprintf(b, "UnitTestsResults/anon%06d.dcm", i); - std::auto_ptr<ParsedDicomFile> f(o.Clone()); - if (i > 4) - o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); - m.Apply(*f); - f->SaveToFile(b); - } -} - - -#include <dcmtk/dcmdata/dcuid.h> - -TEST(DicomModification, Png) -{ - // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image) - std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; - - std::string m, c; - Toolbox::DecodeDataUriScheme(m, c, s); - - ASSERT_EQ("image/png", m); - ASSERT_EQ(116, c.size()); - - std::string cc; - Toolbox::DecodeBase64(cc, c); - PngReader reader; - reader.ReadFromMemory(cc); - - ASSERT_EQ(5, reader.GetHeight()); - ASSERT_EQ(5, reader.GetWidth()); - ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); - - ParsedDicomFile o; - o.EmbedImage(s); - o.SaveToFile("UnitTestsResults/png1.dcm"); - - // Red dot, without alpha channel - s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII="; - o.EmbedImage(s); - o.SaveToFile("UnitTestsResults/png2.dcm"); - - // Check box in Graylevel8 - s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="; - o.EmbedImage(s); - //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); - o.SaveToFile("UnitTestsResults/png3.dcm"); - - - { - // Gradient in Graylevel16 - - ImageBuffer img; - img.SetWidth(256); - img.SetHeight(256); - img.SetFormat(PixelFormat_Grayscale16); - - int v = 0; - for (unsigned int y = 0; y < img.GetHeight(); y++) - { - uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y)); - for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++) - { - *p = v; - } - } - - o.EmbedImage(img.GetAccessor()); - o.SaveToFile("UnitTestsResults/png4.dcm"); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,147 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../OrthancServer/FromDcmtkBridge.h" +#include "../OrthancServer/OrthancInitialization.h" +#include "../OrthancServer/DicomModification.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngReader.h" +#include "../Core/ImageFormats/PngWriter.h" + +using namespace Orthanc; + +TEST(DicomFormat, Tag) +{ + ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); + + DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); + ASSERT_EQ(0x0008, t.GetGroup()); + ASSERT_EQ(0x103E, t.GetElement()); + + t = FromDcmtkBridge::ParseTag("0020-e040"); + ASSERT_EQ(0x0020, t.GetGroup()); + ASSERT_EQ(0xe040, t.GetElement()); + + // Test ==() and !=() operators + ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); + ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); +} + + +TEST(DicomModification, Basic) +{ + DicomModification m; + m.SetupAnonymization(); + //m.SetLevel(DicomRootLevel_Study); + //m.Replace(DICOM_TAG_PATIENT_ID, "coucou"); + //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); + + ParsedDicomFile o; + o.SaveToFile("UnitTestsResults/anon.dcm"); + + for (int i = 0; i < 10; i++) + { + char b[1024]; + sprintf(b, "UnitTestsResults/anon%06d.dcm", i); + std::auto_ptr<ParsedDicomFile> f(o.Clone()); + if (i > 4) + o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); + m.Apply(*f); + f->SaveToFile(b); + } +} + + +#include <dcmtk/dcmdata/dcuid.h> + +TEST(DicomModification, Png) +{ + // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image) + std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; + + std::string m, c; + Toolbox::DecodeDataUriScheme(m, c, s); + + ASSERT_EQ("image/png", m); + ASSERT_EQ(116, c.size()); + + std::string cc; + Toolbox::DecodeBase64(cc, c); + PngReader reader; + reader.ReadFromMemory(cc); + + ASSERT_EQ(5, reader.GetHeight()); + ASSERT_EQ(5, reader.GetWidth()); + ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); + + ParsedDicomFile o; + o.EmbedImage(s); + o.SaveToFile("UnitTestsResults/png1.dcm"); + + // Red dot, without alpha channel + s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII="; + o.EmbedImage(s); + o.SaveToFile("UnitTestsResults/png2.dcm"); + + // Check box in Graylevel8 + s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="; + o.EmbedImage(s); + //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); + o.SaveToFile("UnitTestsResults/png3.dcm"); + + + { + // Gradient in Graylevel16 + + ImageBuffer img; + img.SetWidth(256); + img.SetHeight(256); + img.SetFormat(PixelFormat_Grayscale16); + + int v = 0; + for (unsigned int y = 0; y < img.GetHeight(); y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y)); + for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++) + { + *p = v; + } + } + + o.EmbedImage(img.GetAccessor()); + o.SaveToFile("UnitTestsResults/png4.dcm"); + } +}
--- a/UnitTestsSources/JpegLossless.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../OrthancServer/Internals/DicomImageDecoder.h" - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 - -#include <dcmtk/dcmdata/dcfilefo.h> - -#include "../OrthancServer/ParsedDicomFile.h" -#include "../Core/OrthancException.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/PngWriter.h" - -using namespace Orthanc; - - - -// TODO Write a test - - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/JpegLosslessTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../OrthancServer/Internals/DicomImageDecoder.h" + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + +#include <dcmtk/dcmdata/dcfilefo.h> + +#include "../OrthancServer/ParsedDicomFile.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngWriter.h" + +using namespace Orthanc; + + + +// TODO Write a test + + +#endif
--- a/UnitTestsSources/Lua.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Lua/LuaFunctionCall.h" - - -TEST(Lua, Json) -{ - Orthanc::LuaContext lua; - lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); - lua.Execute("a={}"); - lua.Execute("a['x'] = 10"); - lua.Execute("a['y'] = {}"); - lua.Execute("a['y'][1] = 20"); - lua.Execute("a['y'][2] = 20"); - lua.Execute("PrintRecursive(a)"); - - lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end"); - - Json::Value v, vv, o; - //v["a"] = "b"; - v.append("hello"); - v.append("world"); - v.append("42"); - vv.append("sub"); - vv.append("set"); - v.append(vv); - o = Json::objectValue; - o["x"] = 10; - o["y"] = 20; - o["z"] = 20.5f; - v.append(o); - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushJSON(v); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); - } - - o["bool"] = false; - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_FALSE(f.ExecutePredicate()); - } - - o["bool"] = true; - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_TRUE(f.ExecutePredicate()); - } -} - - -TEST(Lua, Existing) -{ - Orthanc::LuaContext lua; - lua.Execute("a={}"); - lua.Execute("function f() end"); - - ASSERT_TRUE(lua.IsExistingFunction("f")); - ASSERT_FALSE(lua.IsExistingFunction("a")); - ASSERT_FALSE(lua.IsExistingFunction("Dummy")); -} - - -TEST(Lua, Simple) -{ - Orthanc::LuaContext lua; - lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushString("hello"); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushBoolean(true); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushInteger(42); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushDouble(3.1415); - f.Execute(); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/LuaTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,136 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Lua/LuaFunctionCall.h" + + +TEST(Lua, Json) +{ + Orthanc::LuaContext lua; + lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + lua.Execute("a={}"); + lua.Execute("a['x'] = 10"); + lua.Execute("a['y'] = {}"); + lua.Execute("a['y'][1] = 20"); + lua.Execute("a['y'][2] = 20"); + lua.Execute("PrintRecursive(a)"); + + lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end"); + + Json::Value v, vv, o; + //v["a"] = "b"; + v.append("hello"); + v.append("world"); + v.append("42"); + vv.append("sub"); + vv.append("set"); + v.append(vv); + o = Json::objectValue; + o["x"] = 10; + o["y"] = 20; + o["z"] = 20.5f; + v.append(o); + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushJSON(v); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); + } + + o["bool"] = false; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_FALSE(f.ExecutePredicate()); + } + + o["bool"] = true; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_TRUE(f.ExecutePredicate()); + } +} + + +TEST(Lua, Existing) +{ + Orthanc::LuaContext lua; + lua.Execute("a={}"); + lua.Execute("function f() end"); + + ASSERT_TRUE(lua.IsExistingFunction("f")); + ASSERT_FALSE(lua.IsExistingFunction("a")); + ASSERT_FALSE(lua.IsExistingFunction("Dummy")); +} + + +TEST(Lua, Simple) +{ + Orthanc::LuaContext lua; + lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushString("hello"); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushBoolean(true); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushInteger(42); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushDouble(3.1415); + f.Execute(); + } +}
--- a/UnitTestsSources/MemoryCache.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,230 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include <glog/logging.h> -#include <memory> -#include <boost/thread.hpp> -#include <boost/lexical_cast.hpp> -#include "../Core/IDynamicObject.h" -#include "../Core/Cache/MemoryCache.h" - - -TEST(LRU, Basic) -{ - Orthanc::LeastRecentlyUsedIndex<std::string> r; - - r.Add("d"); - r.Add("a"); - r.Add("c"); - r.Add("b"); - - r.MakeMostRecent("a"); - r.MakeMostRecent("d"); - r.MakeMostRecent("b"); - r.MakeMostRecent("c"); - r.MakeMostRecent("d"); - r.MakeMostRecent("c"); - - ASSERT_EQ("a", r.GetOldest()); - ASSERT_EQ("a", r.RemoveOldest()); - ASSERT_EQ("b", r.GetOldest()); - ASSERT_EQ("b", r.RemoveOldest()); - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ("d", r.RemoveOldest()); - ASSERT_EQ("c", r.GetOldest()); - ASSERT_EQ("c", r.RemoveOldest()); - - ASSERT_TRUE(r.IsEmpty()); - - ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException); - ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException); -} - - -TEST(LRU, Payload) -{ - Orthanc::LeastRecentlyUsedIndex<std::string, int> r; - - r.Add("a", 420); - r.Add("b", 421); - r.Add("c", 422); - r.Add("d", 423); - - r.MakeMostRecent("a"); - r.MakeMostRecent("d"); - r.MakeMostRecent("b"); - r.MakeMostRecent("c"); - r.MakeMostRecent("d"); - r.MakeMostRecent("c"); - - ASSERT_TRUE(r.Contains("b")); - ASSERT_EQ(421, r.Invalidate("b")); - ASSERT_FALSE(r.Contains("b")); - - int p; - ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p); - ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p); - ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p); - - ASSERT_EQ("a", r.GetOldest()); - ASSERT_EQ(420, r.GetOldestPayload()); - ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); - - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ(423, r.GetOldestPayload()); - ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); - - ASSERT_EQ("c", r.GetOldest()); - ASSERT_EQ(422, r.GetOldestPayload()); - ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); - - ASSERT_TRUE(r.IsEmpty()); -} - - -TEST(LRU, PayloadUpdate) -{ - Orthanc::LeastRecentlyUsedIndex<std::string, int> r; - - r.Add("a", 420); - r.Add("b", 421); - r.Add("d", 423); - - r.MakeMostRecent("a", 424); - r.MakeMostRecent("d", 421); - - ASSERT_EQ("b", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("a", r.GetOldest()); - ASSERT_EQ(424, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_TRUE(r.IsEmpty()); -} - - - -TEST(LRU, PayloadUpdateBis) -{ - Orthanc::LeastRecentlyUsedIndex<std::string, int> r; - - r.AddOrMakeMostRecent("a", 420); - r.AddOrMakeMostRecent("b", 421); - r.AddOrMakeMostRecent("d", 423); - r.AddOrMakeMostRecent("a", 424); - r.AddOrMakeMostRecent("d", 421); - - ASSERT_EQ("b", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("a", r.GetOldest()); - ASSERT_EQ(424, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_TRUE(r.IsEmpty()); -} - - - - -namespace -{ - class Integer : public Orthanc::IDynamicObject - { - private: - std::string& log_; - int value_; - - public: - Integer(std::string& log, int v) : log_(log), value_(v) - { - } - - virtual ~Integer() - { - LOG(INFO) << "Removing cache entry for " << value_; - log_ += boost::lexical_cast<std::string>(value_) + " "; - } - - int GetValue() const - { - return value_; - } - }; - - class IntegerProvider : public Orthanc::ICachePageProvider - { - public: - std::string log_; - - Orthanc::IDynamicObject* Provide(const std::string& s) - { - LOG(INFO) << "Providing " << s; - return new Integer(log_, boost::lexical_cast<int>(s)); - } - }; -} - - -TEST(MemoryCache, Basic) -{ - IntegerProvider provider; - - { - Orthanc::MemoryCache cache(provider, 3); - cache.Access("42"); // 42 -> exit - cache.Access("43"); // 43, 42 -> exit - cache.Access("45"); // 45, 43, 42 -> exit - cache.Access("42"); // 42, 45, 43 -> exit - cache.Access("43"); // 43, 42, 45 -> exit - cache.Access("47"); // 45 is removed; 47, 43, 42 -> exit - cache.Access("44"); // 42 is removed; 44, 47, 43 -> exit - cache.Access("42"); // 43 is removed; 42, 44, 47 -> exit - // Closing the cache: 47, 44, 42 are successively removed - } - - ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/MemoryCacheTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,230 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include <glog/logging.h> +#include <memory> +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include "../Core/IDynamicObject.h" +#include "../Core/Cache/MemoryCache.h" + + +TEST(LRU, Basic) +{ + Orthanc::LeastRecentlyUsedIndex<std::string> r; + + r.Add("d"); + r.Add("a"); + r.Add("c"); + r.Add("b"); + + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ("a", r.RemoveOldest()); + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ("b", r.RemoveOldest()); + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ("d", r.RemoveOldest()); + ASSERT_EQ("c", r.GetOldest()); + ASSERT_EQ("c", r.RemoveOldest()); + + ASSERT_TRUE(r.IsEmpty()); + + ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException); + ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException); +} + + +TEST(LRU, Payload) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("c", 422); + r.Add("d", 423); + + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); + + ASSERT_TRUE(r.Contains("b")); + ASSERT_EQ(421, r.Invalidate("b")); + ASSERT_FALSE(r.Contains("b")); + + int p; + ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p); + ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p); + ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(420, r.GetOldestPayload()); + ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(423, r.GetOldestPayload()); + ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); + + ASSERT_EQ("c", r.GetOldest()); + ASSERT_EQ(422, r.GetOldestPayload()); + ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); + + ASSERT_TRUE(r.IsEmpty()); +} + + +TEST(LRU, PayloadUpdate) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("d", 423); + + r.MakeMostRecent("a", 424); + r.MakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + + +TEST(LRU, PayloadUpdateBis) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.AddOrMakeMostRecent("a", 420); + r.AddOrMakeMostRecent("b", 421); + r.AddOrMakeMostRecent("d", 423); + r.AddOrMakeMostRecent("a", 424); + r.AddOrMakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + + + +namespace +{ + class Integer : public Orthanc::IDynamicObject + { + private: + std::string& log_; + int value_; + + public: + Integer(std::string& log, int v) : log_(log), value_(v) + { + } + + virtual ~Integer() + { + LOG(INFO) << "Removing cache entry for " << value_; + log_ += boost::lexical_cast<std::string>(value_) + " "; + } + + int GetValue() const + { + return value_; + } + }; + + class IntegerProvider : public Orthanc::ICachePageProvider + { + public: + std::string log_; + + Orthanc::IDynamicObject* Provide(const std::string& s) + { + LOG(INFO) << "Providing " << s; + return new Integer(log_, boost::lexical_cast<int>(s)); + } + }; +} + + +TEST(MemoryCache, Basic) +{ + IntegerProvider provider; + + { + Orthanc::MemoryCache cache(provider, 3); + cache.Access("42"); // 42 -> exit + cache.Access("43"); // 43, 42 -> exit + cache.Access("45"); // 45, 43, 42 -> exit + cache.Access("42"); // 42, 45, 43 -> exit + cache.Access("43"); // 43, 42, 45 -> exit + cache.Access("47"); // 45 is removed; 47, 43, 42 -> exit + cache.Access("44"); // 42 is removed; 44, 47, 43 -> exit + cache.Access("42"); // 43 is removed; 42, 44, 47 -> exit + // Closing the cache: 47, 44, 42 are successively removed + } + + ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); +}
--- a/UnitTestsSources/MultiThreading.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,276 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include <glog/logging.h> - -#include "../Core/OrthancException.h" -#include "../Core/Toolbox.h" -#include "../Core/MultiThreading/ArrayFilledByThreads.h" -#include "../Core/MultiThreading/Locker.h" -#include "../Core/MultiThreading/Mutex.h" -#include "../Core/MultiThreading/ReaderWriterLock.h" -#include "../Core/MultiThreading/ThreadedCommandProcessor.h" - -using namespace Orthanc; - -namespace -{ - class DynamicInteger : public ICommand - { - private: - int value_; - std::set<int>& target_; - - public: - DynamicInteger(int value, std::set<int>& target) : - value_(value), target_(target) - { - } - - int GetValue() const - { - return value_; - } - - virtual bool Execute() - { - static boost::mutex mutex; - boost::mutex::scoped_lock lock(mutex); - target_.insert(value_); - return true; - } - }; - - class MyFiller : public ArrayFilledByThreads::IFiller - { - private: - int size_; - unsigned int created_; - std::set<int> set_; - - public: - MyFiller(int size) : size_(size), created_(0) - { - } - - virtual size_t GetFillerSize() - { - return size_; - } - - virtual IDynamicObject* GetFillerItem(size_t index) - { - static boost::mutex mutex; - boost::mutex::scoped_lock lock(mutex); - created_++; - return new DynamicInteger(index * 2, set_); - } - - unsigned int GetCreatedCount() const - { - return created_; - } - - std::set<int> GetSet() - { - return set_; - } - }; -} - - - - -TEST(MultiThreading, SharedMessageQueueBasic) -{ - std::set<int> s; - - SharedMessageQueue q; - ASSERT_TRUE(q.WaitEmpty(0)); - q.Enqueue(new DynamicInteger(10, s)); - ASSERT_FALSE(q.WaitEmpty(1)); - q.Enqueue(new DynamicInteger(20, s)); - q.Enqueue(new DynamicInteger(30, s)); - q.Enqueue(new DynamicInteger(40, s)); - - std::auto_ptr<DynamicInteger> i; - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); - ASSERT_FALSE(q.WaitEmpty(1)); - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); - ASSERT_TRUE(q.WaitEmpty(0)); - ASSERT_EQ(NULL, q.Dequeue(1)); -} - - -TEST(MultiThreading, SharedMessageQueueClean) -{ - std::set<int> s; - - try - { - SharedMessageQueue q; - q.Enqueue(new DynamicInteger(10, s)); - q.Enqueue(new DynamicInteger(20, s)); - throw OrthancException("Nope"); - } - catch (OrthancException&) - { - } -} - - -TEST(MultiThreading, ArrayFilledByThreadEmpty) -{ - MyFiller f(0); - ArrayFilledByThreads a(f); - a.SetThreadCount(1); - ASSERT_EQ(0, a.GetSize()); -} - - -TEST(MultiThreading, ArrayFilledByThread1) -{ - MyFiller f(100); - ArrayFilledByThreads a(f); - a.SetThreadCount(1); - ASSERT_EQ(100, a.GetSize()); - for (size_t i = 0; i < a.GetSize(); i++) - { - ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); - } -} - - -TEST(MultiThreading, ArrayFilledByThread4) -{ - MyFiller f(100); - ArrayFilledByThreads a(f); - a.SetThreadCount(4); - ASSERT_EQ(100, a.GetSize()); - for (size_t i = 0; i < a.GetSize(); i++) - { - ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); - } - - ASSERT_EQ(100u, f.GetCreatedCount()); - - a.Invalidate(); - - ASSERT_EQ(100, a.GetSize()); - ASSERT_EQ(200u, f.GetCreatedCount()); - ASSERT_EQ(4u, a.GetThreadCount()); - ASSERT_TRUE(f.GetSet().empty()); - - for (size_t i = 0; i < a.GetSize(); i++) - { - ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); - } -} - - -TEST(MultiThreading, CommandProcessor) -{ - ThreadedCommandProcessor p(4); - - std::set<int> s; - - for (size_t i = 0; i < 100; i++) - { - p.Post(new DynamicInteger(i * 2, s)); - } - - p.Join(); - - for (size_t i = 0; i < 200; i++) - { - if (i % 2) - ASSERT_TRUE(s.find(i) == s.end()); - else - ASSERT_TRUE(s.find(i) != s.end()); - } -} - - -TEST(MultiThreading, Mutex) -{ - Mutex mutex; - Locker locker(mutex); -} - - -TEST(MultiThreading, ReaderWriterLock) -{ - ReaderWriterLock lock; - - { - Locker locker1(lock.ForReader()); - Locker locker2(lock.ForReader()); - } - - { - Locker locker3(lock.ForWriter()); - } -} - - - - - -#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" - -TEST(ReusableDicomUserConnection, DISABLED_Basic) -{ - ReusableDicomUserConnection c; - c.SetMillisecondsBeforeClose(200); - printf("START\n"); fflush(stdout); - { - ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); - } - - printf("**\n"); fflush(stdout); - Toolbox::USleep(1000000); - printf("**\n"); fflush(stdout); - - { - ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); - } - - Toolbox::ServerBarrier(); - printf("DONE\n"); fflush(stdout); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/MultiThreadingTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,276 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include <glog/logging.h> + +#include "../Core/OrthancException.h" +#include "../Core/Toolbox.h" +#include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/Locker.h" +#include "../Core/MultiThreading/Mutex.h" +#include "../Core/MultiThreading/ReaderWriterLock.h" +#include "../Core/MultiThreading/ThreadedCommandProcessor.h" + +using namespace Orthanc; + +namespace +{ + class DynamicInteger : public ICommand + { + private: + int value_; + std::set<int>& target_; + + public: + DynamicInteger(int value, std::set<int>& target) : + value_(value), target_(target) + { + } + + int GetValue() const + { + return value_; + } + + virtual bool Execute() + { + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); + target_.insert(value_); + return true; + } + }; + + class MyFiller : public ArrayFilledByThreads::IFiller + { + private: + int size_; + unsigned int created_; + std::set<int> set_; + + public: + MyFiller(int size) : size_(size), created_(0) + { + } + + virtual size_t GetFillerSize() + { + return size_; + } + + virtual IDynamicObject* GetFillerItem(size_t index) + { + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); + created_++; + return new DynamicInteger(index * 2, set_); + } + + unsigned int GetCreatedCount() const + { + return created_; + } + + std::set<int> GetSet() + { + return set_; + } + }; +} + + + + +TEST(MultiThreading, SharedMessageQueueBasic) +{ + std::set<int> s; + + SharedMessageQueue q; + ASSERT_TRUE(q.WaitEmpty(0)); + q.Enqueue(new DynamicInteger(10, s)); + ASSERT_FALSE(q.WaitEmpty(1)); + q.Enqueue(new DynamicInteger(20, s)); + q.Enqueue(new DynamicInteger(30, s)); + q.Enqueue(new DynamicInteger(40, s)); + + std::auto_ptr<DynamicInteger> i; + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); + ASSERT_FALSE(q.WaitEmpty(1)); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); + ASSERT_TRUE(q.WaitEmpty(0)); + ASSERT_EQ(NULL, q.Dequeue(1)); +} + + +TEST(MultiThreading, SharedMessageQueueClean) +{ + std::set<int> s; + + try + { + SharedMessageQueue q; + q.Enqueue(new DynamicInteger(10, s)); + q.Enqueue(new DynamicInteger(20, s)); + throw OrthancException("Nope"); + } + catch (OrthancException&) + { + } +} + + +TEST(MultiThreading, ArrayFilledByThreadEmpty) +{ + MyFiller f(0); + ArrayFilledByThreads a(f); + a.SetThreadCount(1); + ASSERT_EQ(0, a.GetSize()); +} + + +TEST(MultiThreading, ArrayFilledByThread1) +{ + MyFiller f(100); + ArrayFilledByThreads a(f); + a.SetThreadCount(1); + ASSERT_EQ(100, a.GetSize()); + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); + } +} + + +TEST(MultiThreading, ArrayFilledByThread4) +{ + MyFiller f(100); + ArrayFilledByThreads a(f); + a.SetThreadCount(4); + ASSERT_EQ(100, a.GetSize()); + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); + } + + ASSERT_EQ(100u, f.GetCreatedCount()); + + a.Invalidate(); + + ASSERT_EQ(100, a.GetSize()); + ASSERT_EQ(200u, f.GetCreatedCount()); + ASSERT_EQ(4u, a.GetThreadCount()); + ASSERT_TRUE(f.GetSet().empty()); + + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue()); + } +} + + +TEST(MultiThreading, CommandProcessor) +{ + ThreadedCommandProcessor p(4); + + std::set<int> s; + + for (size_t i = 0; i < 100; i++) + { + p.Post(new DynamicInteger(i * 2, s)); + } + + p.Join(); + + for (size_t i = 0; i < 200; i++) + { + if (i % 2) + ASSERT_TRUE(s.find(i) == s.end()); + else + ASSERT_TRUE(s.find(i) != s.end()); + } +} + + +TEST(MultiThreading, Mutex) +{ + Mutex mutex; + Locker locker(mutex); +} + + +TEST(MultiThreading, ReaderWriterLock) +{ + ReaderWriterLock lock; + + { + Locker locker1(lock.ForReader()); + Locker locker2(lock.ForReader()); + } + + { + Locker locker3(lock.ForWriter()); + } +} + + + + + +#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" + +TEST(ReusableDicomUserConnection, DISABLED_Basic) +{ + ReusableDicomUserConnection c; + c.SetMillisecondsBeforeClose(200); + printf("START\n"); fflush(stdout); + { + ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); + } + + printf("**\n"); fflush(stdout); + Toolbox::USleep(1000000); + printf("**\n"); fflush(stdout); + + { + ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); + } + + Toolbox::ServerBarrier(); + printf("DONE\n"); fflush(stdout); +}
--- a/UnitTestsSources/Png.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include <stdint.h> -#include "../Core/ImageFormats/PngReader.h" -#include "../Core/ImageFormats/PngWriter.h" -#include "../Core/Toolbox.h" -#include "../Core/Uuid.h" - - -TEST(PngWriter, ColorPattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 61; - int pitch = width * 3; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p += 3) - { - p[0] = (y % 3 == 0) ? 255 : 0; - p[1] = (y % 3 == 1) ? 255 : 0; - p[2] = (y % 3 == 2) ? 255 : 0; - } - } - - w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); -} - -TEST(PngWriter, Gray8Pattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 256; - int pitch = width; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p++) - { - *p = y; - } - } - - w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); -} - -TEST(PngWriter, Gray16Pattern) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector<uint8_t> image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); -} - -TEST(PngWriter, EndToEnd) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector<uint8_t> image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - std::string s; - w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); - - { - Orthanc::PngReader r; - r.ReadFromMemory(s); - - ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); - ASSERT_EQ(r.GetWidth(), width); - ASSERT_EQ(r.GetHeight(), height); - - v = 0; - for (int y = 0; y < height; y++) - { - const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); - ASSERT_EQ(p, r.GetConstRow(y)); - for (int x = 0; x < width; x++, p++, v++) - { - ASSERT_EQ(*p, v); - } - } - } - - { - Orthanc::Toolbox::TemporaryFile tmp; - Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); - - Orthanc::PngReader r2; - r2.ReadFromFile(tmp.GetPath()); - - ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); - ASSERT_EQ(r2.GetWidth(), width); - ASSERT_EQ(r2.GetHeight(), height); - - v = 0; - for (int y = 0; y < height; y++) - { - const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); - ASSERT_EQ(p, r2.GetConstRow(y)); - for (int x = 0; x < width; x++, p++, v++) - { - ASSERT_EQ(*p, v); - } - } - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/PngTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,186 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include <stdint.h> +#include "../Core/ImageFormats/PngReader.h" +#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Toolbox.h" +#include "../Core/Uuid.h" + + +TEST(PngWriter, ColorPattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 61; + int pitch = width * 3; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p += 3) + { + p[0] = (y % 3 == 0) ? 255 : 0; + p[1] = (y % 3 == 1) ? 255 : 0; + p[2] = (y % 3 == 2) ? 255 : 0; + } + } + + w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); +} + +TEST(PngWriter, Gray8Pattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 256; + int pitch = width; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p++) + { + *p = y; + } + } + + w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); +} + +TEST(PngWriter, Gray16Pattern) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); +} + +TEST(PngWriter, EndToEnd) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + std::string s; + w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + { + Orthanc::PngReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); + + v = 0; + for (int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); + ASSERT_EQ(p, r.GetConstRow(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } + + { + Orthanc::Toolbox::TemporaryFile tmp; + Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); + + Orthanc::PngReader r2; + r2.ReadFromFile(tmp.GetPath()); + + ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r2.GetWidth(), width); + ASSERT_EQ(r2.GetHeight(), height); + + v = 0; + for (int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); + ASSERT_EQ(p, r2.GetConstRow(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } +}
--- a/UnitTestsSources/RestApi.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include <ctype.h> -#include <glog/logging.h> - -#include "../Core/ChunkedBuffer.h" -#include "../Core/HttpClient.h" -#include "../Core/RestApi/RestApi.h" -#include "../Core/Uuid.h" -#include "../Core/OrthancException.h" -#include "../Core/Compression/ZlibCompressor.h" - -using namespace Orthanc; - -#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) -#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" -#endif - -TEST(HttpClient, Basic) -{ - HttpClient c; - ASSERT_FALSE(c.IsVerbose()); - c.SetVerbose(true); - ASSERT_TRUE(c.IsVerbose()); - c.SetVerbose(false); - ASSERT_FALSE(c.IsVerbose()); - -#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 - Json::Value v; - c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json"); - c.Apply(v); - ASSERT_TRUE(v.isMember("StorageDirectory")); - //ASSERT_EQ(GetLastStatusText()); - - v = Json::nullValue; - - HttpClient cc(c); - cc.SetUrl("https://orthanc.googlecode.com/hg/Resources/Configuration.json"); - cc.Apply(v); - ASSERT_TRUE(v.isMember("LuaScripts")); -#endif -} - -TEST(RestApi, ChunkedBuffer) -{ - ChunkedBuffer b; - ASSERT_EQ(0, b.GetNumBytes()); - - b.AddChunk("hello", 5); - ASSERT_EQ(5, b.GetNumBytes()); - - b.AddChunk("world", 5); - ASSERT_EQ(10, b.GetNumBytes()); - - std::string s; - b.Flatten(s); - ASSERT_EQ("helloworld", s); -} - -TEST(RestApi, ParseCookies) -{ - HttpHandler::Arguments headers; - HttpHandler::Arguments cookies; - - headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(4u, cookies.size()); - ASSERT_EQ("b", cookies["a"]); - ASSERT_EQ("d", cookies["c"]); - ASSERT_EQ("f", cookies["e"]); - ASSERT_EQ("h", cookies["g"]); - - headers["cookie"] = " name = value ; name2=value2"; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(2u, cookies.size()); - ASSERT_EQ("value", cookies["name"]); - ASSERT_EQ("value2", cookies["name2"]); - - headers["cookie"] = " ;;; "; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(0u, cookies.size()); - - headers["cookie"] = " ; n=v ;; "; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(1u, cookies.size()); - ASSERT_EQ("v", cookies["n"]); -} - -TEST(RestApi, RestApiPath) -{ - RestApiPath::Components args; - UriComponents trail; - - { - RestApiPath uri("/coucou/{abc}/d/*"); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); - ASSERT_EQ(1u, args.size()); - ASSERT_EQ(3u, trail.size()); - ASSERT_EQ("moi", args["abc"]); - ASSERT_EQ("e", trail[0]); - ASSERT_EQ("f", trail[1]); - ASSERT_EQ("g", trail[2]); - - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f")); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/")); - ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d")); - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi")); - } - - { - RestApiPath uri("/coucou/{abc}/d"); - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d")); - ASSERT_EQ(1u, args.size()); - ASSERT_EQ(0u, trail.size()); - ASSERT_EQ("moi", args["abc"]); - } - - { - RestApiPath uri("/*"); - ASSERT_TRUE(uri.Match(args, trail, "/a/b/c")); - ASSERT_EQ(0u, args.size()); - ASSERT_EQ(3u, trail.size()); - ASSERT_EQ("a", trail[0]); - ASSERT_EQ("b", trail[1]); - ASSERT_EQ("c", trail[2]); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/RestApiTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,265 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include <ctype.h> +#include <glog/logging.h> + +#include "../Core/ChunkedBuffer.h" +#include "../Core/HttpClient.h" +#include "../Core/RestApi/RestApi.h" +#include "../Core/Uuid.h" +#include "../Core/OrthancException.h" +#include "../Core/Compression/ZlibCompressor.h" +#include "../Core/RestApi/RestApiHierarchy.h" + +using namespace Orthanc; + +#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) +#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" +#endif + +TEST(HttpClient, Basic) +{ + HttpClient c; + ASSERT_FALSE(c.IsVerbose()); + c.SetVerbose(true); + ASSERT_TRUE(c.IsVerbose()); + c.SetVerbose(false); + ASSERT_FALSE(c.IsVerbose()); + +#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 + Json::Value v; + c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json"); + c.Apply(v); + ASSERT_TRUE(v.isMember("StorageDirectory")); + //ASSERT_EQ(GetLastStatusText()); + + v = Json::nullValue; + + HttpClient cc(c); + cc.SetUrl("https://orthanc.googlecode.com/hg/Resources/Configuration.json"); + cc.Apply(v); + ASSERT_TRUE(v.isMember("LuaScripts")); +#endif +} + +TEST(RestApi, ChunkedBuffer) +{ + ChunkedBuffer b; + ASSERT_EQ(0, b.GetNumBytes()); + + b.AddChunk("hello", 5); + ASSERT_EQ(5, b.GetNumBytes()); + + b.AddChunk("world", 5); + ASSERT_EQ(10, b.GetNumBytes()); + + std::string s; + b.Flatten(s); + ASSERT_EQ("helloworld", s); +} + +TEST(RestApi, ParseCookies) +{ + HttpHandler::Arguments headers; + HttpHandler::Arguments cookies; + + headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(4u, cookies.size()); + ASSERT_EQ("b", cookies["a"]); + ASSERT_EQ("d", cookies["c"]); + ASSERT_EQ("f", cookies["e"]); + ASSERT_EQ("h", cookies["g"]); + + headers["cookie"] = " name = value ; name2=value2"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(2u, cookies.size()); + ASSERT_EQ("value", cookies["name"]); + ASSERT_EQ("value2", cookies["name2"]); + + headers["cookie"] = " ;;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(0u, cookies.size()); + + headers["cookie"] = " ; n=v ;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(1u, cookies.size()); + ASSERT_EQ("v", cookies["n"]); +} + +TEST(RestApi, RestApiPath) +{ + RestApiPath::Components args; + UriComponents trail; + + { + RestApiPath uri("/coucou/{abc}/d/*"); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); + ASSERT_EQ(1u, args.size()); + ASSERT_EQ(3u, trail.size()); + ASSERT_EQ("moi", args["abc"]); + ASSERT_EQ("e", trail[0]); + ASSERT_EQ("f", trail[1]); + ASSERT_EQ("g", trail[2]); + + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f")); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/")); + ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d")); + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi")); + + ASSERT_EQ(3u, uri.GetLevelCount()); + ASSERT_TRUE(uri.IsUniversalTrailing()); + + ASSERT_EQ("coucou", uri.GetLevelName(0)); + ASSERT_THROW(uri.GetWildcardName(0), OrthancException); + + ASSERT_EQ("abc", uri.GetWildcardName(1)); + ASSERT_THROW(uri.GetLevelName(1), OrthancException); + + ASSERT_EQ("d", uri.GetLevelName(2)); + ASSERT_THROW(uri.GetWildcardName(2), OrthancException); + } + + { + RestApiPath uri("/coucou/{abc}/d"); + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d")); + ASSERT_EQ(1u, args.size()); + ASSERT_EQ(0u, trail.size()); + ASSERT_EQ("moi", args["abc"]); + + ASSERT_EQ(3u, uri.GetLevelCount()); + ASSERT_FALSE(uri.IsUniversalTrailing()); + + ASSERT_EQ("coucou", uri.GetLevelName(0)); + ASSERT_THROW(uri.GetWildcardName(0), OrthancException); + + ASSERT_EQ("abc", uri.GetWildcardName(1)); + ASSERT_THROW(uri.GetLevelName(1), OrthancException); + + ASSERT_EQ("d", uri.GetLevelName(2)); + ASSERT_THROW(uri.GetWildcardName(2), OrthancException); + } + + { + RestApiPath uri("/*"); + ASSERT_TRUE(uri.Match(args, trail, "/a/b/c")); + ASSERT_EQ(0u, args.size()); + ASSERT_EQ(3u, trail.size()); + ASSERT_EQ("a", trail[0]); + ASSERT_EQ("b", trail[1]); + ASSERT_EQ("c", trail[2]); + + ASSERT_EQ(0u, uri.GetLevelCount()); + ASSERT_TRUE(uri.IsUniversalTrailing()); + } +} + + + + + + +static int testValue; + +template <int value> +static void SetValue(RestApi::GetCall& get) +{ + testValue = value; +} + + +static bool GetDirectory(Json::Value& target, + RestApiHierarchy& hierarchy, + const std::string& uri) +{ + UriComponents p; + Toolbox::SplitUriComponents(p, uri); + return hierarchy.GetDirectory(target, p); +} + + +static bool HandleGet(RestApiHierarchy& hierarchy, + const std::string& uri) +{ + UriComponents p; + Toolbox::SplitUriComponents(p, uri); + return hierarchy.Handle(*reinterpret_cast<RestApi::GetCall*>(NULL), p); +} + + +TEST(RestApi, RestApiHierarchy) +{ + RestApiHierarchy root; + root.Register("/hello/world/test", SetValue<1>); + root.Register("/hello/world/test2", SetValue<2>); + root.Register("/hello/{world}/test3/test4", SetValue<3>); + root.Register("/hello2/*", SetValue<4>); + + Json::Value m; + root.CreateSiteMap(m); + std::cout << m; + + Json::Value d; + ASSERT_FALSE(GetDirectory(d, root, "/hello")); + + ASSERT_TRUE(GetDirectory(d, root, "/hello/a")); + ASSERT_EQ(1u, d.size()); + ASSERT_EQ("test3", d[0].asString()); + + ASSERT_TRUE(GetDirectory(d, root, "/hello/world")); + ASSERT_EQ(2u, d.size()); + + ASSERT_TRUE(GetDirectory(d, root, "/hello/a/test3")); + ASSERT_EQ(1u, d.size()); + ASSERT_EQ("test4", d[0].asString()); + + ASSERT_FALSE(GetDirectory(d, root, "/hello/world/test")); + ASSERT_FALSE(GetDirectory(d, root, "/hello/world/test2")); + ASSERT_FALSE(GetDirectory(d, root, "/hello2")); + + testValue = 0; + ASSERT_TRUE(HandleGet(root, "/hello/world/test")); + ASSERT_EQ(testValue, 1); + ASSERT_TRUE(HandleGet(root, "/hello/world/test2")); + ASSERT_EQ(testValue, 2); + ASSERT_TRUE(HandleGet(root, "/hello/b/test3/test4")); + ASSERT_EQ(testValue, 3); + ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test")); + ASSERT_EQ(testValue, 3); + ASSERT_TRUE(HandleGet(root, "/hello2/a/b")); + ASSERT_EQ(testValue, 4); +}
--- a/UnitTestsSources/SQLite.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,334 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Toolbox.h" -#include "../Core/SQLite/Connection.h" -#include "../Core/SQLite/Statement.h" -#include "../Core/SQLite/Transaction.h" - -#include <sqlite3.h> - -using namespace Orthanc; - - -TEST(SQLite, Configuration) -{ - ASSERT_EQ(1, sqlite3_threadsafe()); -} - - -TEST(SQLite, Connection) -{ - Toolbox::RemoveFile("UnitTestsResults/coucou"); - SQLite::Connection c; - c.Open("UnitTestsResults/coucou"); - c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)"); - c.Execute("INSERT INTO c VALUES(NULL, 42);"); -} - - -TEST(SQLite, StatementReferenceBasic) -{ - sqlite3* db; - sqlite3_open(":memory:", &db); - - { - SQLite::StatementReference r(db, "SELECT * FROM sqlite_master"); - ASSERT_EQ(0u, r.GetReferenceCount()); - - { - SQLite::StatementReference r1(r); - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - - { - SQLite::StatementReference r2(r); - ASSERT_EQ(2u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - - SQLite::StatementReference r3(r2); - ASSERT_EQ(3u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - ASSERT_EQ(0u, r3.GetReferenceCount()); - } - - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - - { - SQLite::StatementReference r2(r); - ASSERT_EQ(2u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - } - - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - } - - ASSERT_EQ(0u, r.GetReferenceCount()); - } - - sqlite3_close(db); -} - -TEST(SQLite, StatementBasic) -{ - SQLite::Connection c; - c.OpenInMemory(); - - SQLite::Statement s(c, "SELECT * from sqlite_master"); - s.Run(); - - for (unsigned int i = 0; i < 5; i++) - { - SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master"); - cs.Step(); - } -} - - -namespace -{ - static bool destroyed; - - class MyFunc : public SQLite::IScalarFunction - { - public: - MyFunc() - { - destroyed = false; - } - - virtual ~MyFunc() - { - destroyed = true; - } - - virtual const char* GetName() const - { - return "MYFUNC"; - } - - virtual unsigned int GetCardinality() const - { - return 2; - } - - virtual void Compute(SQLite::FunctionContext& context) - { - context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1)); - } - }; - - class MyDelete : public SQLite::IScalarFunction - { - public: - std::set<int> deleted_; - - virtual const char* GetName() const - { - return "MYDELETE"; - } - - virtual unsigned int GetCardinality() const - { - return 1; - } - - virtual void Compute(SQLite::FunctionContext& context) - { - deleted_.insert(context.GetIntValue(0)); - context.SetNullResult(); - } - }; -} - -TEST(SQLite, ScalarFunction) -{ - { - SQLite::Connection c; - c.OpenInMemory(); - c.Register(new MyFunc()); - c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);"); - c.Execute("INSERT INTO t VALUES(NULL, 2, 3);"); - c.Execute("INSERT INTO t VALUES(NULL, 4, 4);"); - c.Execute("INSERT INTO t VALUES(NULL, 6, 5);"); - SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t"); - int i = 0; - while (t.Step()) - { - ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2)); - i++; - } - ASSERT_EQ(3, i); - ASSERT_FALSE(destroyed); - } - ASSERT_TRUE(destroyed); -} - -TEST(SQLite, CascadedDeleteCallback) -{ - SQLite::Connection c; - c.OpenInMemory(); - MyDelete *func = new MyDelete(); - c.Register(func); - c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);"); - c.Execute("CREATE TABLE child(" - " id INTEGER PRIMARY KEY, " - " parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, " - " value INTEGER);"); - c.Execute("CREATE TRIGGER childRemoved " - "AFTER DELETE ON child " - "FOR EACH ROW BEGIN " - " SELECT MYDELETE(old.value); " - "END;"); - - c.Execute("INSERT INTO parent VALUES(42, 100);"); - c.Execute("INSERT INTO parent VALUES(43, 101);"); - - c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);"); - c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);"); - - c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);"); - c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);"); - - // The following command deletes "parent(43, 101)", then in turns - // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and - // 4301 - c.Execute("DELETE FROM parent WHERE dummy=101"); - - ASSERT_EQ(2u, func->deleted_.size()); - ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end()); - ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end()); -} - - -TEST(SQLite, EmptyTransactions) -{ - try - { - SQLite::Connection c; - c.OpenInMemory(); - - c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);"); - c.Execute("INSERT INTO a VALUES(NULL)"); - - { - SQLite::Transaction t(c); - t.Begin(); - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); - s.Step(); - } - //t.Commit(); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); - s.Step(); - } - } - catch (OrthancException& e) - { - fprintf(stderr, "Exception: [%s]\n", e.What()); - throw e; - } -} - - -TEST(SQLite, Types) -{ - SQLite::Connection c; - c.OpenInMemory(); - c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY, value)"); - - { - SQLite::Statement s(c, std::string("SELECT * FROM a")); - ASSERT_EQ(2, s.ColumnCount()); - ASSERT_FALSE(s.Step()); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); - ASSERT_FALSE(s.Step()); - ASSERT_EQ("SELECT * FROM a", s.GetOriginalSQLStatement()); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "INSERT INTO a VALUES(NULL, ?);"); - s.BindNull(0); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindBool(0, true); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindInt(0, 42); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindInt64(0, 42ll); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindDouble(0, 42.5); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindCString(0, "Hello"); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindBlob(0, "Hello", 5); ASSERT_TRUE(s.Run()); s.Reset(); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_NULL, s.GetColumnType(1)); - ASSERT_TRUE(s.ColumnIsNull(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); - ASSERT_TRUE(s.ColumnBool(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); - ASSERT_EQ(42, s.ColumnInt(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); - ASSERT_EQ(42ll, s.ColumnInt64(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1)); - ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1)); - ASSERT_EQ("Hello", s.ColumnString(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_BLOB, s.GetColumnType(1)); - ASSERT_EQ(5, s.ColumnByteLength(1)); - ASSERT_TRUE(!memcmp("Hello", s.ColumnBlob(1), 5)); - - std::string t; - ASSERT_TRUE(s.ColumnBlobAsString(1, &t)); - ASSERT_EQ("Hello", t); - - ASSERT_FALSE(s.Step()); - } -}
--- a/UnitTestsSources/SQLiteChromium.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,377 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Toolbox.h" -#include "../Core/SQLite/Connection.h" -#include "../Core/SQLite/Statement.h" -#include "../Core/SQLite/Transaction.h" - -#include <sqlite3.h> - - -using namespace Orthanc; -using namespace Orthanc::SQLite; - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc - ********************************************************************/ - -class SQLConnectionTest : public testing::Test -{ -public: - SQLConnectionTest() - { - } - - virtual ~SQLConnectionTest() - { - } - - virtual void SetUp() - { - db_.OpenInMemory(); - } - - virtual void TearDown() - { - db_.Close(); - } - - Connection& db() - { - return db_; - } - -private: - Connection db_; -}; - - - -TEST_F(SQLConnectionTest, Execute) -{ - // Valid statement should return true. - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - EXPECT_EQ(SQLITE_OK, db().GetErrorCode()); - - // Invalid statement should fail. - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b")); - EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode()); -} - -TEST_F(SQLConnectionTest, ExecuteWithErrorCode) { - ASSERT_EQ(SQLITE_OK, - db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)")); - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE")); - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode( - "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)")); -} - -TEST_F(SQLConnectionTest, CachedStatement) { - StatementId id1("foo", 12); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)")); - - // Create a new cached statement. - { - Statement s(db(), id1, "SELECT a FROM foo"); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - } - - // The statement should be cached still. - EXPECT_TRUE(db().HasCachedStatement(id1)); - - { - // Get the same statement using different SQL. This should ignore our - // SQL and use the cached one (so it will be valid). - Statement s(db(), id1, "something invalid("); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - } - - // Make sure other statements aren't marked as cached. - EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE)); -} - -TEST_F(SQLConnectionTest, IsSQLValidTest) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo")); - ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo")); -} - - - -TEST_F(SQLConnectionTest, DoesStuffExist) { - // Test DoesTableExist. - EXPECT_FALSE(db().DoesTableExist("foo")); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - EXPECT_TRUE(db().DoesTableExist("foo")); - - // Should be case sensitive. - EXPECT_FALSE(db().DoesTableExist("FOO")); - - // Test DoesColumnExist. - EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); - EXPECT_TRUE(db().DoesColumnExist("foo", "a")); - - // Testing for a column on a nonexistent table. - EXPECT_FALSE(db().DoesColumnExist("bar", "b")); -} - -TEST_F(SQLConnectionTest, GetLastInsertRowId) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)")); - - ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); - - // Last insert row ID should be valid. - int64_t row = db().GetLastInsertRowId(); - EXPECT_LT(0, row); - - // It should be the primary key of the row we just inserted. - Statement s(db(), "SELECT value FROM foo WHERE id=?"); - s.BindInt64(0, row); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); -} - -TEST_F(SQLConnectionTest, Rollback) { - ASSERT_TRUE(db().BeginTransaction()); - ASSERT_TRUE(db().BeginTransaction()); - EXPECT_EQ(2, db().GetTransactionNesting()); - db().RollbackTransaction(); - EXPECT_FALSE(db().CommitTransaction()); - EXPECT_TRUE(db().BeginTransaction()); -} - - - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc - ********************************************************************/ - -namespace Orthanc -{ - namespace SQLite - { - class SQLStatementTest : public SQLConnectionTest - { - }; - - TEST_F(SQLStatementTest, Run) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); - - Statement s(db(), "SELECT b FROM foo WHERE a=?"); - // Stepping it won't work since we haven't bound the value. - EXPECT_FALSE(s.Step()); - - // Run should fail since this produces output, and we should use Step(). This - // gets a bit wonky since sqlite says this is OK so succeeded is set. - s.Reset(true); - s.BindInt(0, 3); - EXPECT_FALSE(s.Run()); - EXPECT_EQ(SQLITE_ROW, db().GetErrorCode()); - - // Resetting it should put it back to the previous state (not runnable). - s.Reset(true); - - // Binding and stepping should produce one row. - s.BindInt(0, 3); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); - } - - TEST_F(SQLStatementTest, BasicErrorCallback) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); - // Insert in the foo table the primary key. It is an error to insert - // something other than an number. This error causes the error callback - // handler to be called with SQLITE_MISMATCH as error code. - Statement s(db(), "INSERT INTO foo (a) VALUES (?)"); - s.BindCString(0, "bad bad"); - EXPECT_THROW(s.Run(), OrthancException); - } - - TEST_F(SQLStatementTest, Reset) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); - - Statement s(db(), "SELECT b FROM foo WHERE a = ? "); - s.BindInt(0, 3); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - ASSERT_FALSE(s.Step()); - - s.Reset(false); - // Verify that we can get all rows again. - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); - - s.Reset(true); - ASSERT_FALSE(s.Step()); - } - } -} - - - - - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc - ********************************************************************/ - -class SQLTransactionTest : public SQLConnectionTest -{ -public: - virtual void SetUp() - { - SQLConnectionTest::SetUp(); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - } - - // Returns the number of rows in table "foo". - int CountFoo() - { - Statement count(db(), "SELECT count(*) FROM foo"); - count.Step(); - return count.ColumnInt(0); - } -}; - - -TEST_F(SQLTransactionTest, Commit) { - { - Transaction t(db()); - EXPECT_FALSE(t.IsOpen()); - t.Begin(); - EXPECT_TRUE(t.IsOpen()); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - - t.Commit(); - EXPECT_FALSE(t.IsOpen()); - } - - EXPECT_EQ(1, CountFoo()); -} - -TEST_F(SQLTransactionTest, Rollback) { - // Test some basic initialization, and that rollback runs when you exit the - // scope. - { - Transaction t(db()); - EXPECT_FALSE(t.IsOpen()); - t.Begin(); - EXPECT_TRUE(t.IsOpen()); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - } - - // Nothing should have been committed since it was implicitly rolled back. - EXPECT_EQ(0, CountFoo()); - - // Test explicit rollback. - Transaction t2(db()); - EXPECT_FALSE(t2.IsOpen()); - t2.Begin(); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - t2.Rollback(); - EXPECT_FALSE(t2.IsOpen()); - - // Nothing should have been committed since it was explicitly rolled back. - EXPECT_EQ(0, CountFoo()); -} - -// Rolling back any part of a transaction should roll back all of them. -TEST_F(SQLTransactionTest, NestedRollback) { - EXPECT_EQ(0, db().GetTransactionNesting()); - - // Outermost transaction. - { - Transaction outer(db()); - outer.Begin(); - EXPECT_EQ(1, db().GetTransactionNesting()); - - // The first inner one gets committed. - { - Transaction inner1(db()); - inner1.Begin(); - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - EXPECT_EQ(2, db().GetTransactionNesting()); - - inner1.Commit(); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - - // One row should have gotten inserted. - EXPECT_EQ(1, CountFoo()); - - // The second inner one gets rolled back. - { - Transaction inner2(db()); - inner2.Begin(); - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - EXPECT_EQ(2, db().GetTransactionNesting()); - - inner2.Rollback(); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - - // A third inner one will fail in Begin since one has already been rolled - // back. - EXPECT_EQ(1, db().GetTransactionNesting()); - { - Transaction inner3(db()); - EXPECT_THROW(inner3.Begin(), OrthancException); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - } - EXPECT_EQ(0, db().GetTransactionNesting()); - EXPECT_EQ(0, CountFoo()); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/SQLiteChromiumTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,377 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Toolbox.h" +#include "../Core/SQLite/Connection.h" +#include "../Core/SQLite/Statement.h" +#include "../Core/SQLite/Transaction.h" + +#include <sqlite3.h> + + +using namespace Orthanc; +using namespace Orthanc::SQLite; + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc + ********************************************************************/ + +class SQLConnectionTest : public testing::Test +{ +public: + SQLConnectionTest() + { + } + + virtual ~SQLConnectionTest() + { + } + + virtual void SetUp() + { + db_.OpenInMemory(); + } + + virtual void TearDown() + { + db_.Close(); + } + + Connection& db() + { + return db_; + } + +private: + Connection db_; +}; + + + +TEST_F(SQLConnectionTest, Execute) +{ + // Valid statement should return true. + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_EQ(SQLITE_OK, db().GetErrorCode()); + + // Invalid statement should fail. + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b")); + EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode()); +} + +TEST_F(SQLConnectionTest, ExecuteWithErrorCode) { + ASSERT_EQ(SQLITE_OK, + db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode( + "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)")); +} + +TEST_F(SQLConnectionTest, CachedStatement) { + StatementId id1("foo", 12); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)")); + + // Create a new cached statement. + { + Statement s(db(), id1, "SELECT a FROM foo"); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // The statement should be cached still. + EXPECT_TRUE(db().HasCachedStatement(id1)); + + { + // Get the same statement using different SQL. This should ignore our + // SQL and use the cached one (so it will be valid). + Statement s(db(), id1, "something invalid("); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // Make sure other statements aren't marked as cached. + EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE)); +} + +TEST_F(SQLConnectionTest, IsSQLValidTest) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo")); + ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo")); +} + + + +TEST_F(SQLConnectionTest, DoesStuffExist) { + // Test DoesTableExist. + EXPECT_FALSE(db().DoesTableExist("foo")); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_TRUE(db().DoesTableExist("foo")); + + // Should be case sensitive. + EXPECT_FALSE(db().DoesTableExist("FOO")); + + // Test DoesColumnExist. + EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); + EXPECT_TRUE(db().DoesColumnExist("foo", "a")); + + // Testing for a column on a nonexistent table. + EXPECT_FALSE(db().DoesColumnExist("bar", "b")); +} + +TEST_F(SQLConnectionTest, GetLastInsertRowId) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)")); + + ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); + + // Last insert row ID should be valid. + int64_t row = db().GetLastInsertRowId(); + EXPECT_LT(0, row); + + // It should be the primary key of the row we just inserted. + Statement s(db(), "SELECT value FROM foo WHERE id=?"); + s.BindInt64(0, row); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); +} + +TEST_F(SQLConnectionTest, Rollback) { + ASSERT_TRUE(db().BeginTransaction()); + ASSERT_TRUE(db().BeginTransaction()); + EXPECT_EQ(2, db().GetTransactionNesting()); + db().RollbackTransaction(); + EXPECT_FALSE(db().CommitTransaction()); + EXPECT_TRUE(db().BeginTransaction()); +} + + + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc + ********************************************************************/ + +namespace Orthanc +{ + namespace SQLite + { + class SQLStatementTest : public SQLConnectionTest + { + }; + + TEST_F(SQLStatementTest, Run) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + + Statement s(db(), "SELECT b FROM foo WHERE a=?"); + // Stepping it won't work since we haven't bound the value. + EXPECT_FALSE(s.Step()); + + // Run should fail since this produces output, and we should use Step(). This + // gets a bit wonky since sqlite says this is OK so succeeded is set. + s.Reset(true); + s.BindInt(0, 3); + EXPECT_FALSE(s.Run()); + EXPECT_EQ(SQLITE_ROW, db().GetErrorCode()); + + // Resetting it should put it back to the previous state (not runnable). + s.Reset(true); + + // Binding and stepping should produce one row. + s.BindInt(0, 3); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + } + + TEST_F(SQLStatementTest, BasicErrorCallback) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); + // Insert in the foo table the primary key. It is an error to insert + // something other than an number. This error causes the error callback + // handler to be called with SQLITE_MISMATCH as error code. + Statement s(db(), "INSERT INTO foo (a) VALUES (?)"); + s.BindCString(0, "bad bad"); + EXPECT_THROW(s.Run(), OrthancException); + } + + TEST_F(SQLStatementTest, Reset) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); + + Statement s(db(), "SELECT b FROM foo WHERE a = ? "); + s.BindInt(0, 3); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + ASSERT_FALSE(s.Step()); + + s.Reset(false); + // Verify that we can get all rows again. + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + + s.Reset(true); + ASSERT_FALSE(s.Step()); + } + } +} + + + + + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc + ********************************************************************/ + +class SQLTransactionTest : public SQLConnectionTest +{ +public: + virtual void SetUp() + { + SQLConnectionTest::SetUp(); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + } + + // Returns the number of rows in table "foo". + int CountFoo() + { + Statement count(db(), "SELECT count(*) FROM foo"); + count.Step(); + return count.ColumnInt(0); + } +}; + + +TEST_F(SQLTransactionTest, Commit) { + { + Transaction t(db()); + EXPECT_FALSE(t.IsOpen()); + t.Begin(); + EXPECT_TRUE(t.IsOpen()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + + t.Commit(); + EXPECT_FALSE(t.IsOpen()); + } + + EXPECT_EQ(1, CountFoo()); +} + +TEST_F(SQLTransactionTest, Rollback) { + // Test some basic initialization, and that rollback runs when you exit the + // scope. + { + Transaction t(db()); + EXPECT_FALSE(t.IsOpen()); + t.Begin(); + EXPECT_TRUE(t.IsOpen()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + } + + // Nothing should have been committed since it was implicitly rolled back. + EXPECT_EQ(0, CountFoo()); + + // Test explicit rollback. + Transaction t2(db()); + EXPECT_FALSE(t2.IsOpen()); + t2.Begin(); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + t2.Rollback(); + EXPECT_FALSE(t2.IsOpen()); + + // Nothing should have been committed since it was explicitly rolled back. + EXPECT_EQ(0, CountFoo()); +} + +// Rolling back any part of a transaction should roll back all of them. +TEST_F(SQLTransactionTest, NestedRollback) { + EXPECT_EQ(0, db().GetTransactionNesting()); + + // Outermost transaction. + { + Transaction outer(db()); + outer.Begin(); + EXPECT_EQ(1, db().GetTransactionNesting()); + + // The first inner one gets committed. + { + Transaction inner1(db()); + inner1.Begin(); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().GetTransactionNesting()); + + inner1.Commit(); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + + // One row should have gotten inserted. + EXPECT_EQ(1, CountFoo()); + + // The second inner one gets rolled back. + { + Transaction inner2(db()); + inner2.Begin(); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().GetTransactionNesting()); + + inner2.Rollback(); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + + // A third inner one will fail in Begin since one has already been rolled + // back. + EXPECT_EQ(1, db().GetTransactionNesting()); + { + Transaction inner3(db()); + EXPECT_THROW(inner3.Begin(), OrthancException); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + } + EXPECT_EQ(0, db().GetTransactionNesting()); + EXPECT_EQ(0, CountFoo()); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/SQLiteTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,334 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Toolbox.h" +#include "../Core/SQLite/Connection.h" +#include "../Core/SQLite/Statement.h" +#include "../Core/SQLite/Transaction.h" + +#include <sqlite3.h> + +using namespace Orthanc; + + +TEST(SQLite, Configuration) +{ + ASSERT_EQ(1, sqlite3_threadsafe()); +} + + +TEST(SQLite, Connection) +{ + Toolbox::RemoveFile("UnitTestsResults/coucou"); + SQLite::Connection c; + c.Open("UnitTestsResults/coucou"); + c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)"); + c.Execute("INSERT INTO c VALUES(NULL, 42);"); +} + + +TEST(SQLite, StatementReferenceBasic) +{ + sqlite3* db; + sqlite3_open(":memory:", &db); + + { + SQLite::StatementReference r(db, "SELECT * FROM sqlite_master"); + ASSERT_EQ(0u, r.GetReferenceCount()); + + { + SQLite::StatementReference r1(r); + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + + { + SQLite::StatementReference r2(r); + ASSERT_EQ(2u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + + SQLite::StatementReference r3(r2); + ASSERT_EQ(3u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + ASSERT_EQ(0u, r3.GetReferenceCount()); + } + + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + + { + SQLite::StatementReference r2(r); + ASSERT_EQ(2u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + } + + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + } + + ASSERT_EQ(0u, r.GetReferenceCount()); + } + + sqlite3_close(db); +} + +TEST(SQLite, StatementBasic) +{ + SQLite::Connection c; + c.OpenInMemory(); + + SQLite::Statement s(c, "SELECT * from sqlite_master"); + s.Run(); + + for (unsigned int i = 0; i < 5; i++) + { + SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master"); + cs.Step(); + } +} + + +namespace +{ + static bool destroyed; + + class MyFunc : public SQLite::IScalarFunction + { + public: + MyFunc() + { + destroyed = false; + } + + virtual ~MyFunc() + { + destroyed = true; + } + + virtual const char* GetName() const + { + return "MYFUNC"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(SQLite::FunctionContext& context) + { + context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1)); + } + }; + + class MyDelete : public SQLite::IScalarFunction + { + public: + std::set<int> deleted_; + + virtual const char* GetName() const + { + return "MYDELETE"; + } + + virtual unsigned int GetCardinality() const + { + return 1; + } + + virtual void Compute(SQLite::FunctionContext& context) + { + deleted_.insert(context.GetIntValue(0)); + context.SetNullResult(); + } + }; +} + +TEST(SQLite, ScalarFunction) +{ + { + SQLite::Connection c; + c.OpenInMemory(); + c.Register(new MyFunc()); + c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);"); + c.Execute("INSERT INTO t VALUES(NULL, 2, 3);"); + c.Execute("INSERT INTO t VALUES(NULL, 4, 4);"); + c.Execute("INSERT INTO t VALUES(NULL, 6, 5);"); + SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t"); + int i = 0; + while (t.Step()) + { + ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2)); + i++; + } + ASSERT_EQ(3, i); + ASSERT_FALSE(destroyed); + } + ASSERT_TRUE(destroyed); +} + +TEST(SQLite, CascadedDeleteCallback) +{ + SQLite::Connection c; + c.OpenInMemory(); + MyDelete *func = new MyDelete(); + c.Register(func); + c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);"); + c.Execute("CREATE TABLE child(" + " id INTEGER PRIMARY KEY, " + " parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, " + " value INTEGER);"); + c.Execute("CREATE TRIGGER childRemoved " + "AFTER DELETE ON child " + "FOR EACH ROW BEGIN " + " SELECT MYDELETE(old.value); " + "END;"); + + c.Execute("INSERT INTO parent VALUES(42, 100);"); + c.Execute("INSERT INTO parent VALUES(43, 101);"); + + c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);"); + c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);"); + + c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);"); + c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);"); + + // The following command deletes "parent(43, 101)", then in turns + // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and + // 4301 + c.Execute("DELETE FROM parent WHERE dummy=101"); + + ASSERT_EQ(2u, func->deleted_.size()); + ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end()); + ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end()); +} + + +TEST(SQLite, EmptyTransactions) +{ + try + { + SQLite::Connection c; + c.OpenInMemory(); + + c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);"); + c.Execute("INSERT INTO a VALUES(NULL)"); + + { + SQLite::Transaction t(c); + t.Begin(); + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); + s.Step(); + } + //t.Commit(); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); + s.Step(); + } + } + catch (OrthancException& e) + { + fprintf(stderr, "Exception: [%s]\n", e.What()); + throw e; + } +} + + +TEST(SQLite, Types) +{ + SQLite::Connection c; + c.OpenInMemory(); + c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY, value)"); + + { + SQLite::Statement s(c, std::string("SELECT * FROM a")); + ASSERT_EQ(2, s.ColumnCount()); + ASSERT_FALSE(s.Step()); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); + ASSERT_FALSE(s.Step()); + ASSERT_EQ("SELECT * FROM a", s.GetOriginalSQLStatement()); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "INSERT INTO a VALUES(NULL, ?);"); + s.BindNull(0); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindBool(0, true); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindInt(0, 42); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindInt64(0, 42ll); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindDouble(0, 42.5); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindCString(0, "Hello"); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindBlob(0, "Hello", 5); ASSERT_TRUE(s.Run()); s.Reset(); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_NULL, s.GetColumnType(1)); + ASSERT_TRUE(s.ColumnIsNull(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_TRUE(s.ColumnBool(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_EQ(42, s.ColumnInt(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_EQ(42ll, s.ColumnInt64(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1)); + ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1)); + ASSERT_EQ("Hello", s.ColumnString(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_BLOB, s.GetColumnType(1)); + ASSERT_EQ(5, s.ColumnByteLength(1)); + ASSERT_TRUE(!memcmp("Hello", s.ColumnBlob(1), 5)); + + std::string t; + ASSERT_TRUE(s.ColumnBlobAsString(1, &t)); + ASSERT_EQ("Hello", t); + + ASSERT_FALSE(s.Step()); + } +}
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Jun 25 15:37:48 2014 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -239,7 +239,7 @@ TEST(Uri, SplitUriComponents) { - UriComponents c; + UriComponents c, d; Toolbox::SplitUriComponents(c, "/cou/hello/world"); ASSERT_EQ(3u, c.size()); ASSERT_EQ("cou", c[0]); @@ -280,6 +280,37 @@ } +TEST(Uri, Truncate) +{ + UriComponents c, d; + Toolbox::SplitUriComponents(c, "/cou/hello/world"); + + Toolbox::TruncateUri(d, c, 0); + ASSERT_EQ(3u, d.size()); + ASSERT_EQ("cou", d[0]); + ASSERT_EQ("hello", d[1]); + ASSERT_EQ("world", d[2]); + + Toolbox::TruncateUri(d, c, 1); + ASSERT_EQ(2u, d.size()); + ASSERT_EQ("hello", d[0]); + ASSERT_EQ("world", d[1]); + + Toolbox::TruncateUri(d, c, 2); + ASSERT_EQ(1u, d.size()); + ASSERT_EQ("world", d[0]); + + Toolbox::TruncateUri(d, c, 3); + ASSERT_EQ(0u, d.size()); + + Toolbox::TruncateUri(d, c, 4); + ASSERT_EQ(0u, d.size()); + + Toolbox::TruncateUri(d, c, 5); + ASSERT_EQ(0u, d.size()); +} + + TEST(Uri, Child) { UriComponents c1; Toolbox::SplitUriComponents(c1, "/hello/world");
--- a/UnitTestsSources/Versions.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include <stdint.h> -#include <math.h> -#include <png.h> -#include <ctype.h> -#include <zlib.h> -#include <curl/curl.h> -#include <boost/version.hpp> -#include <sqlite3.h> -#include <lua.h> -#include <openssl/opensslv.h> - - -TEST(Versions, Zlib) -{ - ASSERT_STREQ(zlibVersion(), ZLIB_VERSION); -} - -TEST(Versions, Curl) -{ - curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ(LIBCURL_VERSION, v->version); -} - -TEST(Versions, Png) -{ - ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE, - png_access_version_number()); -} - -TEST(Versions, SQLite) -{ - // http://www.sqlite.org/capi3ref.html#sqlite3_libversion - assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER ); - assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0); - assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0); - - // Ensure that the SQLite version is above 3.7.0. - // "sqlite3_create_function_v2" is not defined in previous versions. - ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000); -} - - -TEST(Versions, Lua) -{ - // Ensure that the Lua version is above 5.1.0. This version has - // introduced some API changes. - ASSERT_GE(LUA_VERSION_NUM, 501); -} - - -#if ORTHANC_STATIC == 1 -TEST(Versions, ZlibStatic) -{ - ASSERT_STREQ("1.2.7", zlibVersion()); -} - -TEST(Versions, BoostStatic) -{ - ASSERT_STREQ("1_55", BOOST_LIB_VERSION); -} - -TEST(Versions, CurlStatic) -{ - curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ("7.26.0", v->version); -} - -TEST(Versions, PngStatic) -{ - ASSERT_EQ(10512, png_access_version_number()); - ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); -} - -TEST(Versions, CurlSslStatic) -{ - curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); - - // Check that SSL support is enabled when required - bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL; - -#if ORTHANC_SSL_ENABLED == 0 - ASSERT_FALSE(curlSupportsSsl); -#else - ASSERT_TRUE(curlSupportsSsl); -#endif -} - -TEST(Version, LuaStatic) -{ - ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); -} - -TEST(Version, OpenSslStatic) -{ - ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER); -} - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/VersionsTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,133 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include <stdint.h> +#include <math.h> +#include <png.h> +#include <ctype.h> +#include <zlib.h> +#include <curl/curl.h> +#include <boost/version.hpp> +#include <sqlite3.h> +#include <lua.h> +#include <openssl/opensslv.h> + + +TEST(Versions, Zlib) +{ + ASSERT_STREQ(zlibVersion(), ZLIB_VERSION); +} + +TEST(Versions, Curl) +{ + curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); + ASSERT_STREQ(LIBCURL_VERSION, v->version); +} + +TEST(Versions, Png) +{ + ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE, + png_access_version_number()); +} + +TEST(Versions, SQLite) +{ + // http://www.sqlite.org/capi3ref.html#sqlite3_libversion + assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER ); + assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0); + assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0); + + // Ensure that the SQLite version is above 3.7.0. + // "sqlite3_create_function_v2" is not defined in previous versions. + ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000); +} + + +TEST(Versions, Lua) +{ + // Ensure that the Lua version is above 5.1.0. This version has + // introduced some API changes. + ASSERT_GE(LUA_VERSION_NUM, 501); +} + + +#if ORTHANC_STATIC == 1 +TEST(Versions, ZlibStatic) +{ + ASSERT_STREQ("1.2.7", zlibVersion()); +} + +TEST(Versions, BoostStatic) +{ + ASSERT_STREQ("1_55", BOOST_LIB_VERSION); +} + +TEST(Versions, CurlStatic) +{ + curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); + ASSERT_STREQ("7.26.0", v->version); +} + +TEST(Versions, PngStatic) +{ + ASSERT_EQ(10512, png_access_version_number()); + ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); +} + +TEST(Versions, CurlSslStatic) +{ + curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); + + // Check that SSL support is enabled when required + bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL; + +#if ORTHANC_SSL_ENABLED == 0 + ASSERT_FALSE(curlSupportsSsl); +#else + ASSERT_TRUE(curlSupportsSsl); +#endif +} + +TEST(Version, LuaStatic) +{ + ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); +} + +TEST(Version, OpenSslStatic) +{ + ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER); +} + +#endif
--- a/UnitTestsSources/Zip.cpp Wed Jun 25 15:37:48 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/OrthancException.h" -#include "../Core/Compression/ZipWriter.h" -#include "../Core/Compression/HierarchicalZipWriter.h" -#include "../Core/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); -} - - - - - -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 <world> ")); - } -} - - -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. - - **/ -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ZipTests.cpp Mon Jun 30 13:36:01 2014 +0200 @@ -0,0 +1,166 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/OrthancException.h" +#include "../Core/Compression/ZipWriter.h" +#include "../Core/Compression/HierarchicalZipWriter.h" +#include "../Core/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); +} + + + + + +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 <world> ")); + } +} + + +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. + + **/ +}