# HG changeset patch # User Sebastien Jodogne # Date 1591878554 -7200 # Node ID 149172a06b4d36c59993388491319c70b0466845 # Parent e241e5f3f088cb402812c72cc1fb3b07fef40492 splitting MultiThreadingTests.cpp into JobsTests.cpp and ServerJobsTests.cpp diff -r e241e5f3f088 -r 149172a06b4d CMakeLists.txt --- a/CMakeLists.txt Thu Jun 11 14:12:07 2020 +0200 +++ b/CMakeLists.txt Thu Jun 11 14:29:14 2020 +0200 @@ -120,6 +120,7 @@ OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp OrthancFramework/UnitTestsSources/ImageTests.cpp + OrthancFramework/UnitTestsSources/JobsTests.cpp OrthancFramework/UnitTestsSources/JpegLosslessTests.cpp OrthancFramework/UnitTestsSources/LoggingTests.cpp OrthancFramework/UnitTestsSources/LuaTests.cpp @@ -135,9 +136,9 @@ set(ORTHANC_SERVER_UNIT_TESTS OrthancServer/UnitTestsSources/DatabaseLookupTests.cpp OrthancServer/UnitTestsSources/LuaServerTests.cpp - OrthancServer/UnitTestsSources/MultiThreadingTests.cpp OrthancServer/UnitTestsSources/PluginsTests.cpp OrthancServer/UnitTestsSources/ServerIndexTests.cpp + OrthancServer/UnitTestsSources/ServerJobsTests.cpp OrthancServer/UnitTestsSources/UnitTestsMain.cpp OrthancServer/UnitTestsSources/VersionsTests.cpp ) diff -r e241e5f3f088 -r 149172a06b4d OrthancFramework/UnitTestsSources/JobsTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Thu Jun 11 14:29:14 2020 +0200 @@ -0,0 +1,1434 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1 +# include +#endif + +#include "gtest/gtest.h" + +#include "../../OrthancFramework/Sources/Compatibility.h" +#include "../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h" +#include "../../OrthancFramework/Sources/DicomParsing/DicomModification.h" +#include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h" +#include "../../OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.h" +#include "../../OrthancFramework/Sources/JobsEngine/JobsEngine.h" +#include "../../OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h" +#include "../../OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h" +#include "../../OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h" +#include "../../OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h" +#include "../../OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h" +#include "../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h" +#include "../../OrthancFramework/Sources/Logging.h" +#include "../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h" +#include "../../OrthancFramework/Sources/OrthancException.h" +#include "../../OrthancFramework/Sources/SerializationToolbox.h" + + +using namespace Orthanc; + +namespace +{ + class DummyJob : public IJob + { + private: + bool fails_; + unsigned int count_; + unsigned int steps_; + + public: + DummyJob() : + fails_(false), + count_(0), + steps_(4) + { + } + + explicit DummyJob(bool fails) : + fails_(fails), + count_(0), + steps_(4) + { + } + + virtual void Start() ORTHANC_OVERRIDE + { + } + + virtual void Reset() ORTHANC_OVERRIDE + { + } + + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE + { + if (fails_) + { + return JobStepResult::Failure(ErrorCode_ParameterOutOfRange, NULL); + } + else if (count_ == steps_ - 1) + { + return JobStepResult::Success(); + } + else + { + count_++; + return JobStepResult::Continue(); + } + } + + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE + { + } + + virtual float GetProgress() ORTHANC_OVERRIDE + { + return static_cast(count_) / static_cast(steps_ - 1); + } + + virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE + { + type = "DummyJob"; + } + + virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE + { + value = Json::objectValue; + value["Type"] = "DummyJob"; + return true; + } + + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE + { + value["hello"] = "world"; + } + + virtual bool GetOutput(std::string& output, + MimeType& mime, + const std::string& key) ORTHANC_OVERRIDE + { + return false; + } + }; + + + class DummyInstancesJob : public SetOfInstancesJob + { + private: + bool trailingStepDone_; + + protected: + virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE + { + return (instance != "nope"); + } + + virtual bool HandleTrailingStep() ORTHANC_OVERRIDE + { + if (HasTrailingStep()) + { + if (trailingStepDone_) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + trailingStepDone_ = true; + return true; + } + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + public: + DummyInstancesJob() : + trailingStepDone_(false) + { + } + + DummyInstancesJob(const Json::Value& value) : + SetOfInstancesJob(value) + { + if (HasTrailingStep()) + { + trailingStepDone_ = (GetPosition() == GetCommandsCount()); + } + else + { + trailingStepDone_ = false; + } + } + + bool IsTrailingStepDone() const + { + return trailingStepDone_; + } + + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE + { + } + + virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE + { + s = "DummyInstancesJob"; + } + }; + + + class DummyUnserializer : public GenericJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& value) ORTHANC_OVERRIDE + { + if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob") + { + return new DummyInstancesJob(value); + } + else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob") + { + return new DummyJob; + } + else + { + return GenericJobUnserializer::UnserializeJob(value); + } + } + }; + + + class DynamicInteger : public IDynamicObject + { + private: + int value_; + std::set& target_; + + public: + DynamicInteger(int value, std::set& target) : + value_(value), target_(target) + { + } + + int GetValue() const + { + return value_; + } + }; +} + + +TEST(MultiThreading, SharedMessageQueueBasic) +{ + std::set 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::unique_ptr i; + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); + ASSERT_FALSE(q.WaitEmpty(1)); + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); + ASSERT_TRUE(q.WaitEmpty(0)); + ASSERT_EQ(NULL, q.Dequeue(1)); +} + + +TEST(MultiThreading, SharedMessageQueueClean) +{ + std::set s; + + try + { + SharedMessageQueue q; + q.Enqueue(new DynamicInteger(10, s)); + q.Enqueue(new DynamicInteger(20, s)); + throw OrthancException(ErrorCode_InternalError); + } + catch (OrthancException&) + { + } +} + + + + +static bool CheckState(JobsRegistry& registry, + const std::string& id, + JobState state) +{ + JobState s; + if (registry.GetState(s, id)) + { + return state == s; + } + else + { + return false; + } +} + + +static bool CheckErrorCode(JobsRegistry& registry, + const std::string& id, + ErrorCode code) +{ + JobInfo s; + if (registry.GetJobInfo(s, id)) + { + return code == s.GetStatus().GetErrorCode(); + } + else + { + return false; + } +} + + +TEST(JobsRegistry, Priority) +{ + JobsRegistry registry(10); + + std::string i1, i2, i3, i4; + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new DummyJob(), 30); + registry.Submit(i3, new DummyJob(), 20); + registry.Submit(i4, new DummyJob(), 5); + + registry.SetMaxCompletedJobs(2); + + std::set id; + registry.ListJobs(id); + + ASSERT_EQ(4u, id.size()); + ASSERT_TRUE(id.find(i1) != id.end()); + ASSERT_TRUE(id.find(i2) != id.end()); + ASSERT_TRUE(id.find(i3) != id.end()); + ASSERT_TRUE(id.find(i4) != id.end()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(30, job.GetPriority()); + ASSERT_EQ(i2, job.GetId()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i2, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i3, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(20, job.GetPriority()); + ASSERT_EQ(i3, job.GetId()); + + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i3, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i3, JobState_Success)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(10, job.GetPriority()); + ASSERT_EQ(i1, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(5, job.GetPriority()); + ASSERT_EQ(i4, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 1); + ASSERT_FALSE(job.IsValid()); + } + + JobState s; + ASSERT_TRUE(registry.GetState(s, i1)); + ASSERT_FALSE(registry.GetState(s, i2)); // Removed because oldest + ASSERT_FALSE(registry.GetState(s, i3)); // Removed because second oldest + ASSERT_TRUE(registry.GetState(s, i4)); + + registry.SetMaxCompletedJobs(1); // (*) + ASSERT_FALSE(registry.GetState(s, i1)); // Just discarded by (*) + ASSERT_TRUE(registry.GetState(s, i4)); +} + + +TEST(JobsRegistry, Simultaneous) +{ + JobsRegistry registry(10); + + std::string i1, i2; + registry.Submit(i1, new DummyJob(), 20); + registry.Submit(i2, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, i1, JobState_Pending)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job1(registry, 0); + JobsRegistry::RunningJob job2(registry, 0); + + ASSERT_TRUE(job1.IsValid()); + ASSERT_TRUE(job2.IsValid()); + + job1.MarkFailure(); + job2.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i1, JobState_Running)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i1, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Success)); +} + + +TEST(JobsRegistry, Resubmit) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkFailure(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Retry) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkRetry(0); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.ScheduleRetries(); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PausePending) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); +} + + +TEST(JobsRegistry, PauseRunning) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + registry.Resubmit(id); + job.MarkPause(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PauseRetry) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkRetry(0); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Cancel) +{ + JobsRegistry registry(10); + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_FALSE(registry.Cancel("nope")); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + registry.Submit(id, new DummyJob(), 10); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkCanceled(); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Pause(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkRetry(500); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); +} + + + +TEST(JobsEngine, SubmitAndWait) +{ + JobsEngine engine(10); + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + Json::Value content = Json::nullValue; + engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10); + ASSERT_EQ(Json::objectValue, content.type()); + ASSERT_EQ("world", content["hello"].asString()); + + content = Json::nullValue; + ASSERT_THROW(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10), OrthancException); + ASSERT_EQ(Json::nullValue, content.type()); + + engine.Stop(); +} + + +TEST(JobsEngine, DISABLED_SequenceOfOperationsJob) +{ + JobsEngine engine(10); + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + std::string id; + SequenceOfOperationsJob* job = NULL; + + { + std::unique_ptr a(new SequenceOfOperationsJob); + job = a.get(); + engine.GetRegistry().Submit(id, a.release(), 0); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + { + SequenceOfOperationsJob::Lock lock(*job); + size_t i = lock.AddOperation(new LogJobOperation); + size_t j = lock.AddOperation(new LogJobOperation); + size_t k = lock.AddOperation(new LogJobOperation); + + StringOperationValue a("Hello"); + StringOperationValue b("World"); + lock.AddInput(i, a); + lock.AddInput(i, b); + + lock.Connect(i, j); + lock.Connect(j, k); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + + engine.Stop(); + +} + + +static bool CheckSameJson(const Json::Value& a, + const Json::Value& b) +{ + std::string s = a.toStyledString(); + std::string t = b.toStyledString(); + + if (s == t) + { + return true; + } + else + { + LOG(ERROR) << "Expected serialization: " << s; + LOG(ERROR) << "Actual serialization: " << t; + return false; + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJob& job) +{ + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::unique_ptr unserialized(unserializer.UnserializeJob(a)); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return (CheckSameJson(a, b)); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer, + SetOfInstancesJob& job) +{ + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::unique_ptr unserialized + (dynamic_cast(unserializer.UnserializeJob(a))); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return (CheckSameJson(a, b) && + job.HasTrailingStep() == unserialized->HasTrailingStep() && + job.GetPosition() == unserialized->GetPosition() && + job.GetInstancesCount() == unserialized->GetInstancesCount() && + job.GetCommandsCount() == unserialized->GetCommandsCount()); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + JobOperationValue& value) +{ + Json::Value a = 42; + value.Serialize(a); + + std::unique_ptr unserialized(unserializer.UnserializeValue(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + + +TEST(JobsSerialization, BadFileFormat) +{ + GenericJobUnserializer unserializer; + + Json::Value s; + + s = Json::objectValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = Json::arrayValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = "hello"; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = 42; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); +} + + +TEST(JobsSerialization, JobOperationValues) +{ + Json::Value s; + + { + JobOperationValues values; + values.Append(new NullOperationValue); + values.Append(new StringOperationValue("hello")); + values.Append(new StringOperationValue("world")); + + s = 42; + values.Serialize(s); + } + + { + GenericJobUnserializer unserializer; + std::unique_ptr values(JobOperationValues::Unserialize(unserializer, s)); + ASSERT_EQ(3u, values->GetSize()); + ASSERT_EQ(JobOperationValue::Type_Null, values->GetValue(0).GetType()); + ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(1).GetType()); + ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(2).GetType()); + + ASSERT_EQ("hello", dynamic_cast(values->GetValue(1)).GetContent()); + ASSERT_EQ("world", dynamic_cast(values->GetValue(2)).GetContent()); + } +} + + +TEST(JobsSerialization, GenericValues) +{ + GenericJobUnserializer unserializer; + Json::Value s; + + { + NullOperationValue null; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, null)); + null.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::unique_ptr value; + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_Null, value->GetType()); + + { + StringOperationValue str("Hello"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, str)); + str.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_String, value->GetType()); + ASSERT_EQ("Hello", dynamic_cast(*value).GetContent()); +} + + +TEST(JobsSerialization, GenericJobs) +{ + Json::Value s; + + // This tests SetOfInstancesJob + + { + DummyInstancesJob job; + job.SetDescription("description"); + job.AddInstance("hello"); + job.AddInstance("nope"); + job.AddInstance("world"); + job.SetPermissive(true); + ASSERT_THROW(job.Step("jobId"), OrthancException); // Not started yet + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + DummyUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + const DummyInstancesJob& tmp = dynamic_cast(*job); + ASSERT_FALSE(tmp.IsStarted()); + ASSERT_TRUE(tmp.IsPermissive()); + ASSERT_EQ("description", tmp.GetDescription()); + ASSERT_EQ(3u, tmp.GetInstancesCount()); + ASSERT_EQ(2u, tmp.GetPosition()); + ASSERT_EQ(1u, tmp.GetFailedInstances().size()); + ASSERT_EQ("hello", tmp.GetInstance(0)); + ASSERT_EQ("nope", tmp.GetInstance(1)); + ASSERT_EQ("world", tmp.GetInstance(2)); + ASSERT_TRUE(tmp.IsFailedInstance("nope")); + } + + // SequenceOfOperationsJob + + { + SequenceOfOperationsJob job; + job.SetDescription("hello"); + + { + SequenceOfOperationsJob::Lock lock(job); + size_t a = lock.AddOperation(new LogJobOperation); + size_t b = lock.AddOperation(new LogJobOperation); + lock.Connect(a, b); + + StringOperationValue s1("hello"); + StringOperationValue s2("world"); + lock.AddInput(a, s1); + lock.AddInput(a, s2); + lock.SetTrailingOperationTimeout(300); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + + { + GenericJobUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + GenericJobUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + std::string tmp; + dynamic_cast(*job).GetDescription(tmp); + ASSERT_EQ("hello", tmp); + } +} + + +static bool IsSameTagValue(ParsedDicomFile& dicom1, + ParsedDicomFile& dicom2, + DicomTag tag) +{ + std::string a, b; + return (dicom1.GetTagValue(a, tag) && + dicom2.GetTagValue(b, tag) && + (a == b)); +} + + + +TEST(JobsSerialization, DicomModification) +{ + Json::Value s; + + ParsedDicomFile source(true); + source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false, ""); + source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false, ""); + source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false, ""); + + std::unique_ptr modified(source.Clone(true)); + + { + DicomModification modification; + modification.SetLevel(ResourceType_Series); + modification.Clear(DICOM_TAG_STUDY_DESCRIPTION); + modification.Remove(DICOM_TAG_SERIES_DESCRIPTION); + modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true); + + modification.Apply(*modified); + + s = 42; + modification.Serialize(s); + } + + { + DicomModification modification(s); + ASSERT_EQ(ResourceType_Series, modification.GetLevel()); + + std::unique_ptr second(source.Clone(true)); + modification.Apply(*second); + + std::string s; + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(s.empty()); + ASSERT_FALSE(second->GetTagValue(s, DICOM_TAG_SERIES_DESCRIPTION)); + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("Test 4", s); + + ASSERT_TRUE(IsSameTagValue(source, *modified, DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(source, *second, DICOM_TAG_STUDY_INSTANCE_UID)); + + ASSERT_FALSE(IsSameTagValue(source, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(*modified, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + } +} + + +TEST(JobsSerialization, Registry) +{ + Json::Value s; + std::string i1, i2; + + { + JobsRegistry registry(10); + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new SequenceOfOperationsJob(), 30); + registry.Serialize(s); + } + + { + DummyUnserializer unserializer; + JobsRegistry registry(unserializer, s, 10); + + Json::Value t; + registry.Serialize(t); + ASSERT_TRUE(CheckSameJson(s, t)); + } +} + + +TEST(JobsSerialization, TrailingStep) +{ + { + Json::Value s; + + DummyInstancesJob job; + ASSERT_EQ(0u, job.GetCommandsCount()); + ASSERT_EQ(0u, job.GetInstancesCount()); + + job.Start(); + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + job.AddInstance("hello"); + job.AddInstance("world"); + ASSERT_EQ(2u, job.GetCommandsCount()); + ASSERT_EQ(2u, job.GetInstancesCount()); + + job.Start(); + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(2u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + ASSERT_EQ(0u, job.GetInstancesCount()); + ASSERT_EQ(0u, job.GetCommandsCount()); + job.AddTrailingStep(); + ASSERT_EQ(0u, job.GetInstancesCount()); + ASSERT_EQ(1u, job.GetCommandsCount()); + + job.Start(); // This adds the trailing step + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_TRUE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_TRUE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + job.AddInstance("hello"); + ASSERT_EQ(1u, job.GetInstancesCount()); + ASSERT_EQ(1u, job.GetCommandsCount()); + job.AddTrailingStep(); + ASSERT_EQ(1u, job.GetInstancesCount()); + ASSERT_EQ(2u, job.GetCommandsCount()); + + job.Start(); + ASSERT_EQ(2u, job.GetCommandsCount()); + ASSERT_EQ(0u, job.GetPosition()); + ASSERT_TRUE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + ASSERT_EQ(2u, job.GetPosition()); + ASSERT_TRUE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step("jobId"), OrthancException); + } +} + + +TEST(JobsSerialization, RemoteModalityParameters) +{ + Json::Value s; + + { + RemoteModalityParameters modality; + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + modality.Serialize(s, false); + ASSERT_EQ(Json::arrayValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + ASSERT_EQ("ORTHANC", modality.GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, modality.GetManufacturer()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } + + s = Json::nullValue; + + { + RemoteModalityParameters modality; + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + ASSERT_THROW(modality.SetPortNumber(0), OrthancException); + ASSERT_THROW(modality.SetPortNumber(65535), OrthancException); + modality.SetApplicationEntityTitle("HELLO"); + modality.SetHost("world"); + modality.SetPortNumber(45); + modality.SetManufacturer(ModalityManufacturer_GenericNoWildcardInDates); + modality.Serialize(s, true); + ASSERT_EQ(Json::objectValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + ASSERT_EQ("HELLO", modality.GetApplicationEntityTitle()); + ASSERT_EQ("world", modality.GetHost()); + ASSERT_EQ(45u, modality.GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_GenericNoWildcardInDates, modality.GetManufacturer()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } + + s["Port"] = "46"; + + { + RemoteModalityParameters modality(s); + ASSERT_EQ(46u, modality.GetPortNumber()); + } + + s["Port"] = -1; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + s["Port"] = 65535; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + s["Port"] = "nope"; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + + std::set operations; + operations.insert(DicomRequestType_Echo); + operations.insert(DicomRequestType_Find); + operations.insert(DicomRequestType_Get); + operations.insert(DicomRequestType_Move); + operations.insert(DicomRequestType_Store); + operations.insert(DicomRequestType_NAction); + operations.insert(DicomRequestType_NEventReport); + + ASSERT_EQ(7u, operations.size()); + + for (std::set::const_iterator + it = operations.begin(); it != operations.end(); ++it) + { + { + RemoteModalityParameters modality; + modality.SetRequestAllowed(*it, false); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + + modality.Serialize(s, false); + ASSERT_EQ(Json::objectValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + + ASSERT_FALSE(modality.IsRequestAllowed(*it)); + + for (std::set::const_iterator + it2 = operations.begin(); it2 != operations.end(); ++it2) + { + if (*it2 != *it) + { + ASSERT_TRUE(modality.IsRequestAllowed(*it2)); + } + } + } + } + + { + Json::Value s; + s["AllowStorageCommitment"] = false; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } + + { + Json::Value s; + s["AllowNAction"] = false; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + s["AllowTranscoding"] = false; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_FALSE(modality.IsTranscodingAllowed()); + } + + { + Json::Value s; + s["AllowNAction"] = true; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); + } +} diff -r e241e5f3f088 -r 149172a06b4d OrthancServer/UnitTestsSources/MultiThreadingTests.cpp --- a/OrthancServer/UnitTestsSources/MultiThreadingTests.cpp Thu Jun 11 14:12:07 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2228 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../../OrthancFramework/Sources/Compatibility.h" -#include "../../OrthancFramework/Sources/FileStorage/MemoryStorageArea.h" -#include "../../OrthancFramework/Sources/JobsEngine/JobsEngine.h" -#include "../../OrthancFramework/Sources/Logging.h" -#include "../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h" -#include "../../OrthancFramework/Sources/OrthancException.h" -#include "../../OrthancFramework/Sources/SerializationToolbox.h" -#include "../../OrthancFramework/Sources/SystemToolbox.h" -#include "../../OrthancFramework/Sources/Toolbox.h" -#include "../Sources/Database/SQLiteDatabaseWrapper.h" -#include "../Sources/ServerContext.h" -#include "../Sources/ServerJobs/LuaJobManager.h" -#include "../Sources/ServerJobs/OrthancJobUnserializer.h" - -#include "../../OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h" -#include "../../OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h" -#include "../../OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h" -#include "../Sources/ServerJobs/Operations/DicomInstanceOperationValue.h" - -#include "../../OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h" -#include "../Sources/ServerJobs/Operations/DeleteResourceOperation.h" -#include "../Sources/ServerJobs/Operations/ModifyInstanceOperation.h" -#include "../Sources/ServerJobs/Operations/StorePeerOperation.h" -#include "../Sources/ServerJobs/Operations/StoreScuOperation.h" -#include "../Sources/ServerJobs/Operations/SystemCallOperation.h" - -#include "../Sources/ServerJobs/ArchiveJob.h" -#include "../Sources/ServerJobs/DicomModalityStoreJob.h" -#include "../Sources/ServerJobs/DicomMoveScuJob.h" -#include "../Sources/ServerJobs/MergeStudyJob.h" -#include "../Sources/ServerJobs/OrthancPeerStoreJob.h" -#include "../Sources/ServerJobs/ResourceModificationJob.h" -#include "../Sources/ServerJobs/SplitStudyJob.h" - - -using namespace Orthanc; - -namespace -{ - class DummyJob : public IJob - { - private: - bool fails_; - unsigned int count_; - unsigned int steps_; - - public: - DummyJob() : - fails_(false), - count_(0), - steps_(4) - { - } - - explicit DummyJob(bool fails) : - fails_(fails), - count_(0), - steps_(4) - { - } - - virtual void Start() ORTHANC_OVERRIDE - { - } - - virtual void Reset() ORTHANC_OVERRIDE - { - } - - virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE - { - if (fails_) - { - return JobStepResult::Failure(ErrorCode_ParameterOutOfRange, NULL); - } - else if (count_ == steps_ - 1) - { - return JobStepResult::Success(); - } - else - { - count_++; - return JobStepResult::Continue(); - } - } - - virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE - { - } - - virtual float GetProgress() ORTHANC_OVERRIDE - { - return static_cast(count_) / static_cast(steps_ - 1); - } - - virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE - { - type = "DummyJob"; - } - - virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE - { - value = Json::objectValue; - value["Type"] = "DummyJob"; - return true; - } - - virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE - { - value["hello"] = "world"; - } - - virtual bool GetOutput(std::string& output, - MimeType& mime, - const std::string& key) ORTHANC_OVERRIDE - { - return false; - } - }; - - - class DummyInstancesJob : public SetOfInstancesJob - { - private: - bool trailingStepDone_; - - protected: - virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE - { - return (instance != "nope"); - } - - virtual bool HandleTrailingStep() ORTHANC_OVERRIDE - { - if (HasTrailingStep()) - { - if (trailingStepDone_) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - trailingStepDone_ = true; - return true; - } - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - } - - public: - DummyInstancesJob() : - trailingStepDone_(false) - { - } - - DummyInstancesJob(const Json::Value& value) : - SetOfInstancesJob(value) - { - if (HasTrailingStep()) - { - trailingStepDone_ = (GetPosition() == GetCommandsCount()); - } - else - { - trailingStepDone_ = false; - } - } - - bool IsTrailingStepDone() const - { - return trailingStepDone_; - } - - virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE - { - } - - virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE - { - s = "DummyInstancesJob"; - } - }; - - - class DummyUnserializer : public GenericJobUnserializer - { - public: - virtual IJob* UnserializeJob(const Json::Value& value) ORTHANC_OVERRIDE - { - if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob") - { - return new DummyInstancesJob(value); - } - else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob") - { - return new DummyJob; - } - else - { - return GenericJobUnserializer::UnserializeJob(value); - } - } - }; - - - class DynamicInteger : public IDynamicObject - { - private: - int value_; - std::set& target_; - - public: - DynamicInteger(int value, std::set& target) : - value_(value), target_(target) - { - } - - int GetValue() const - { - return value_; - } - }; -} - - -TEST(MultiThreading, SharedMessageQueueBasic) -{ - std::set 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::unique_ptr i; - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); - ASSERT_FALSE(q.WaitEmpty(1)); - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); - ASSERT_TRUE(q.WaitEmpty(0)); - ASSERT_EQ(NULL, q.Dequeue(1)); -} - - -TEST(MultiThreading, SharedMessageQueueClean) -{ - std::set s; - - try - { - SharedMessageQueue q; - q.Enqueue(new DynamicInteger(10, s)); - q.Enqueue(new DynamicInteger(20, s)); - throw OrthancException(ErrorCode_InternalError); - } - catch (OrthancException&) - { - } -} - - - - -static bool CheckState(JobsRegistry& registry, - const std::string& id, - JobState state) -{ - JobState s; - if (registry.GetState(s, id)) - { - return state == s; - } - else - { - return false; - } -} - - -static bool CheckErrorCode(JobsRegistry& registry, - const std::string& id, - ErrorCode code) -{ - JobInfo s; - if (registry.GetJobInfo(s, id)) - { - return code == s.GetStatus().GetErrorCode(); - } - else - { - return false; - } -} - - -TEST(JobsRegistry, Priority) -{ - JobsRegistry registry(10); - - std::string i1, i2, i3, i4; - registry.Submit(i1, new DummyJob(), 10); - registry.Submit(i2, new DummyJob(), 30); - registry.Submit(i3, new DummyJob(), 20); - registry.Submit(i4, new DummyJob(), 5); - - registry.SetMaxCompletedJobs(2); - - std::set id; - registry.ListJobs(id); - - ASSERT_EQ(4u, id.size()); - ASSERT_TRUE(id.find(i1) != id.end()); - ASSERT_TRUE(id.find(i2) != id.end()); - ASSERT_TRUE(id.find(i3) != id.end()); - ASSERT_TRUE(id.find(i4) != id.end()); - - ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - ASSERT_EQ(30, job.GetPriority()); - ASSERT_EQ(i2, job.GetId()); - - ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, i2, JobState_Failure)); - ASSERT_TRUE(CheckState(registry, i3, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - ASSERT_EQ(20, job.GetPriority()); - ASSERT_EQ(i3, job.GetId()); - - job.MarkSuccess(); - - ASSERT_TRUE(CheckState(registry, i3, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, i3, JobState_Success)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - ASSERT_EQ(10, job.GetPriority()); - ASSERT_EQ(i1, job.GetId()); - } - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - ASSERT_EQ(5, job.GetPriority()); - ASSERT_EQ(i4, job.GetId()); - } - - { - JobsRegistry::RunningJob job(registry, 1); - ASSERT_FALSE(job.IsValid()); - } - - JobState s; - ASSERT_TRUE(registry.GetState(s, i1)); - ASSERT_FALSE(registry.GetState(s, i2)); // Removed because oldest - ASSERT_FALSE(registry.GetState(s, i3)); // Removed because second oldest - ASSERT_TRUE(registry.GetState(s, i4)); - - registry.SetMaxCompletedJobs(1); // (*) - ASSERT_FALSE(registry.GetState(s, i1)); // Just discarded by (*) - ASSERT_TRUE(registry.GetState(s, i4)); -} - - -TEST(JobsRegistry, Simultaneous) -{ - JobsRegistry registry(10); - - std::string i1, i2; - registry.Submit(i1, new DummyJob(), 20); - registry.Submit(i2, new DummyJob(), 10); - - ASSERT_TRUE(CheckState(registry, i1, JobState_Pending)); - ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); - - { - JobsRegistry::RunningJob job1(registry, 0); - JobsRegistry::RunningJob job2(registry, 0); - - ASSERT_TRUE(job1.IsValid()); - ASSERT_TRUE(job2.IsValid()); - - job1.MarkFailure(); - job2.MarkSuccess(); - - ASSERT_TRUE(CheckState(registry, i1, JobState_Running)); - ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, i1, JobState_Failure)); - ASSERT_TRUE(CheckState(registry, i2, JobState_Success)); -} - - -TEST(JobsRegistry, Resubmit) -{ - JobsRegistry registry(10); - - std::string id; - registry.Submit(id, new DummyJob(), 10); - - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - registry.Resubmit(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - job.MarkFailure(); - - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - - registry.Resubmit(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); - - registry.Resubmit(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - ASSERT_EQ(id, job.GetId()); - - job.MarkSuccess(); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Success)); - - registry.Resubmit(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Success)); -} - - -TEST(JobsRegistry, Retry) -{ - JobsRegistry registry(10); - - std::string id; - registry.Submit(id, new DummyJob(), 10); - - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - job.MarkRetry(0); - - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); - - registry.Resubmit(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); - - registry.ScheduleRetries(); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - job.MarkSuccess(); - - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Success)); -} - - -TEST(JobsRegistry, PausePending) -{ - JobsRegistry registry(10); - - std::string id; - registry.Submit(id, new DummyJob(), 10); - - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - registry.Pause(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); - - registry.Pause(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); - - registry.Resubmit(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); - - registry.Resume(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); -} - - -TEST(JobsRegistry, PauseRunning) -{ - JobsRegistry registry(10); - - std::string id; - registry.Submit(id, new DummyJob(), 10); - - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - - registry.Resubmit(id); - job.MarkPause(); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); - - registry.Resubmit(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); - - registry.Resume(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - - job.MarkSuccess(); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Success)); -} - - -TEST(JobsRegistry, PauseRetry) -{ - JobsRegistry registry(10); - - std::string id; - registry.Submit(id, new DummyJob(), 10); - - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - - job.MarkRetry(0); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); - - registry.Pause(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); - - registry.Resume(id); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - - job.MarkSuccess(); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Success)); -} - - -TEST(JobsRegistry, Cancel) -{ - JobsRegistry registry(10); - - std::string id; - registry.Submit(id, new DummyJob(), 10); - - ASSERT_FALSE(registry.Cancel("nope")); - - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); - - ASSERT_TRUE(registry.Cancel(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - ASSERT_TRUE(registry.Cancel(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - ASSERT_TRUE(registry.Resubmit(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); - - job.MarkSuccess(); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Success)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); - - ASSERT_TRUE(registry.Cancel(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Success)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); - - registry.Submit(id, new DummyJob(), 10); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - ASSERT_EQ(id, job.GetId()); - - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - - job.MarkCanceled(); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - ASSERT_TRUE(registry.Resubmit(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - ASSERT_TRUE(registry.Pause(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - ASSERT_TRUE(registry.Cancel(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - ASSERT_TRUE(registry.Resubmit(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); - - { - JobsRegistry::RunningJob job(registry, 0); - ASSERT_TRUE(job.IsValid()); - ASSERT_EQ(id, job.GetId()); - - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); - ASSERT_TRUE(CheckState(registry, id, JobState_Running)); - - job.MarkRetry(500); - } - - ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); - - ASSERT_TRUE(registry.Cancel(id)); - ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); - ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); -} - - - -TEST(JobsEngine, SubmitAndWait) -{ - JobsEngine engine(10); - engine.SetThreadSleep(10); - engine.SetWorkersCount(3); - engine.Start(); - - Json::Value content = Json::nullValue; - engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10); - ASSERT_EQ(Json::objectValue, content.type()); - ASSERT_EQ("world", content["hello"].asString()); - - content = Json::nullValue; - ASSERT_THROW(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10), OrthancException); - ASSERT_EQ(Json::nullValue, content.type()); - - engine.Stop(); -} - - -TEST(JobsEngine, DISABLED_SequenceOfOperationsJob) -{ - JobsEngine engine(10); - engine.SetThreadSleep(10); - engine.SetWorkersCount(3); - engine.Start(); - - std::string id; - SequenceOfOperationsJob* job = NULL; - - { - std::unique_ptr a(new SequenceOfOperationsJob); - job = a.get(); - engine.GetRegistry().Submit(id, a.release(), 0); - } - - boost::this_thread::sleep(boost::posix_time::milliseconds(500)); - - { - SequenceOfOperationsJob::Lock lock(*job); - size_t i = lock.AddOperation(new LogJobOperation); - size_t j = lock.AddOperation(new LogJobOperation); - size_t k = lock.AddOperation(new LogJobOperation); - - StringOperationValue a("Hello"); - StringOperationValue b("World"); - lock.AddInput(i, a); - lock.AddInput(i, b); - - lock.Connect(i, j); - lock.Connect(j, k); - } - - boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); - - engine.Stop(); - -} - - -TEST(JobsEngine, DISABLED_Lua) -{ - JobsEngine engine(10); - engine.SetThreadSleep(10); - engine.SetWorkersCount(2); - engine.Start(); - - LuaJobManager lua; - lua.SetMaxOperationsPerJob(5); - lua.SetTrailingOperationTimeout(200); - - for (size_t i = 0; i < 30; i++) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(150)); - - LuaJobManager::Lock lock(lua, engine); - size_t a = lock.AddLogOperation(); - size_t b = lock.AddLogOperation(); - size_t c = lock.AddSystemCallOperation("echo"); - lock.AddStringInput(a, boost::lexical_cast(i)); - lock.AddNullInput(a); - lock.Connect(a, b); - lock.Connect(a, c); - } - - boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); - - engine.Stop(); -} - - -static bool CheckSameJson(const Json::Value& a, - const Json::Value& b) -{ - std::string s = a.toStyledString(); - std::string t = b.toStyledString(); - - if (s == t) - { - return true; - } - else - { - LOG(ERROR) << "Expected serialization: " << s; - LOG(ERROR) << "Actual serialization: " << t; - return false; - } -} - - -static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, - IJob& job) -{ - Json::Value a = 42; - - if (!job.Serialize(a)) - { - return false; - } - else - { - std::unique_ptr unserialized(unserializer.UnserializeJob(a)); - - Json::Value b = 43; - if (unserialized->Serialize(b)) - { - return (CheckSameJson(a, b)); - } - else - { - return false; - } - } -} - - -static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer, - SetOfInstancesJob& job) -{ - Json::Value a = 42; - - if (!job.Serialize(a)) - { - return false; - } - else - { - std::unique_ptr unserialized - (dynamic_cast(unserializer.UnserializeJob(a))); - - Json::Value b = 43; - if (unserialized->Serialize(b)) - { - return (CheckSameJson(a, b) && - job.HasTrailingStep() == unserialized->HasTrailingStep() && - job.GetPosition() == unserialized->GetPosition() && - job.GetInstancesCount() == unserialized->GetInstancesCount() && - job.GetCommandsCount() == unserialized->GetCommandsCount()); - } - else - { - return false; - } - } -} - - -static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, - IJobOperation& operation) -{ - Json::Value a = 42; - operation.Serialize(a); - - std::unique_ptr unserialized(unserializer.UnserializeOperation(a)); - - Json::Value b = 43; - unserialized->Serialize(b); - - return CheckSameJson(a, b); -} - - -static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, - JobOperationValue& value) -{ - Json::Value a = 42; - value.Serialize(a); - - std::unique_ptr unserialized(unserializer.UnserializeValue(a)); - - Json::Value b = 43; - unserialized->Serialize(b); - - return CheckSameJson(a, b); -} - - -TEST(JobsSerialization, BadFileFormat) -{ - GenericJobUnserializer unserializer; - - Json::Value s; - - s = Json::objectValue; - ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - - s = Json::arrayValue; - ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - - s = "hello"; - ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - - s = 42; - ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); -} - - -TEST(JobsSerialization, JobOperationValues) -{ - Json::Value s; - - { - JobOperationValues values; - values.Append(new NullOperationValue); - values.Append(new StringOperationValue("hello")); - values.Append(new StringOperationValue("world")); - - s = 42; - values.Serialize(s); - } - - { - GenericJobUnserializer unserializer; - std::unique_ptr values(JobOperationValues::Unserialize(unserializer, s)); - ASSERT_EQ(3u, values->GetSize()); - ASSERT_EQ(JobOperationValue::Type_Null, values->GetValue(0).GetType()); - ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(1).GetType()); - ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(2).GetType()); - - ASSERT_EQ("hello", dynamic_cast(values->GetValue(1)).GetContent()); - ASSERT_EQ("world", dynamic_cast(values->GetValue(2)).GetContent()); - } -} - - -TEST(JobsSerialization, GenericValues) -{ - GenericJobUnserializer unserializer; - Json::Value s; - - { - NullOperationValue null; - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, null)); - null.Serialize(s); - } - - ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - - std::unique_ptr value; - value.reset(unserializer.UnserializeValue(s)); - - ASSERT_EQ(JobOperationValue::Type_Null, value->GetType()); - - { - StringOperationValue str("Hello"); - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, str)); - str.Serialize(s); - } - - ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - value.reset(unserializer.UnserializeValue(s)); - - ASSERT_EQ(JobOperationValue::Type_String, value->GetType()); - ASSERT_EQ("Hello", dynamic_cast(*value).GetContent()); -} - - -TEST(JobsSerialization, GenericOperations) -{ - DummyUnserializer unserializer; - Json::Value s; - - { - LogJobOperation operation; - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); - operation.Serialize(s); - } - - ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); - - { - std::unique_ptr operation; - operation.reset(unserializer.UnserializeOperation(s)); - - // Make sure that we have indeed unserialized a log operation - Json::Value dummy; - ASSERT_THROW(dynamic_cast(*operation).Serialize(dummy), std::bad_cast); - dynamic_cast(*operation).Serialize(dummy); - } -} - - -TEST(JobsSerialization, GenericJobs) -{ - Json::Value s; - - // This tests SetOfInstancesJob - - { - DummyInstancesJob job; - job.SetDescription("description"); - job.AddInstance("hello"); - job.AddInstance("nope"); - job.AddInstance("world"); - job.SetPermissive(true); - ASSERT_THROW(job.Step("jobId"), OrthancException); // Not started yet - ASSERT_FALSE(job.HasTrailingStep()); - ASSERT_FALSE(job.IsTrailingStepDone()); - job.Start(); - ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); - ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_TRUE(job.Serialize(s)); - } - - { - DummyUnserializer unserializer; - ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - const DummyInstancesJob& tmp = dynamic_cast(*job); - ASSERT_FALSE(tmp.IsStarted()); - ASSERT_TRUE(tmp.IsPermissive()); - ASSERT_EQ("description", tmp.GetDescription()); - ASSERT_EQ(3u, tmp.GetInstancesCount()); - ASSERT_EQ(2u, tmp.GetPosition()); - ASSERT_EQ(1u, tmp.GetFailedInstances().size()); - ASSERT_EQ("hello", tmp.GetInstance(0)); - ASSERT_EQ("nope", tmp.GetInstance(1)); - ASSERT_EQ("world", tmp.GetInstance(2)); - ASSERT_TRUE(tmp.IsFailedInstance("nope")); - } - - // SequenceOfOperationsJob - - { - SequenceOfOperationsJob job; - job.SetDescription("hello"); - - { - SequenceOfOperationsJob::Lock lock(job); - size_t a = lock.AddOperation(new LogJobOperation); - size_t b = lock.AddOperation(new LogJobOperation); - lock.Connect(a, b); - - StringOperationValue s1("hello"); - StringOperationValue s2("world"); - lock.AddInput(a, s1); - lock.AddInput(a, s2); - lock.SetTrailingOperationTimeout(300); - } - - ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); - - { - GenericJobUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); - } - - ASSERT_TRUE(job.Serialize(s)); - } - - { - GenericJobUnserializer unserializer; - ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); - ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - std::string tmp; - dynamic_cast(*job).GetDescription(tmp); - ASSERT_EQ("hello", tmp); - } -} - - -static bool IsSameTagValue(ParsedDicomFile& dicom1, - ParsedDicomFile& dicom2, - DicomTag tag) -{ - std::string a, b; - return (dicom1.GetTagValue(a, tag) && - dicom2.GetTagValue(b, tag) && - (a == b)); -} - - - -TEST(JobsSerialization, DicomModification) -{ - Json::Value s; - - ParsedDicomFile source(true); - source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false, ""); - source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false, ""); - source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false, ""); - - std::unique_ptr modified(source.Clone(true)); - - { - DicomModification modification; - modification.SetLevel(ResourceType_Series); - modification.Clear(DICOM_TAG_STUDY_DESCRIPTION); - modification.Remove(DICOM_TAG_SERIES_DESCRIPTION); - modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true); - - modification.Apply(*modified); - - s = 42; - modification.Serialize(s); - } - - { - DicomModification modification(s); - ASSERT_EQ(ResourceType_Series, modification.GetLevel()); - - std::unique_ptr second(source.Clone(true)); - modification.Apply(*second); - - std::string s; - ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_STUDY_DESCRIPTION)); - ASSERT_TRUE(s.empty()); - ASSERT_FALSE(second->GetTagValue(s, DICOM_TAG_SERIES_DESCRIPTION)); - ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); - ASSERT_EQ("Test 4", s); - - ASSERT_TRUE(IsSameTagValue(source, *modified, DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(IsSameTagValue(source, *second, DICOM_TAG_STUDY_INSTANCE_UID)); - - ASSERT_FALSE(IsSameTagValue(source, *second, DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(IsSameTagValue(*modified, *second, DICOM_TAG_SERIES_INSTANCE_UID)); - } -} - - -TEST(JobsSerialization, DicomInstanceOrigin) -{ - Json::Value s; - std::string t; - - { - DicomInstanceOrigin origin; - - s = 42; - origin.Serialize(s); - } - - { - DicomInstanceOrigin origin(s); - ASSERT_EQ(RequestOrigin_Unknown, origin.GetRequestOrigin()); - ASSERT_EQ("", std::string(origin.GetRemoteAetC())); - ASSERT_FALSE(origin.LookupRemoteIp(t)); - ASSERT_FALSE(origin.LookupRemoteAet(t)); - ASSERT_FALSE(origin.LookupCalledAet(t)); - ASSERT_FALSE(origin.LookupHttpUsername(t)); - } - - { - DicomInstanceOrigin origin(DicomInstanceOrigin::FromDicomProtocol("host", "aet", "called")); - - s = 42; - origin.Serialize(s); - } - - { - DicomInstanceOrigin origin(s); - ASSERT_EQ(RequestOrigin_DicomProtocol, origin.GetRequestOrigin()); - ASSERT_EQ("aet", std::string(origin.GetRemoteAetC())); - ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); - ASSERT_TRUE(origin.LookupRemoteAet(t)); ASSERT_EQ("aet", t); - ASSERT_TRUE(origin.LookupCalledAet(t)); ASSERT_EQ("called", t); - ASSERT_FALSE(origin.LookupHttpUsername(t)); - } - - { - DicomInstanceOrigin origin(DicomInstanceOrigin::FromHttp("host", "username")); - - s = 42; - origin.Serialize(s); - } - - { - DicomInstanceOrigin origin(s); - ASSERT_EQ(RequestOrigin_RestApi, origin.GetRequestOrigin()); - ASSERT_EQ("", std::string(origin.GetRemoteAetC())); - ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); - ASSERT_FALSE(origin.LookupRemoteAet(t)); - ASSERT_FALSE(origin.LookupCalledAet(t)); - ASSERT_TRUE(origin.LookupHttpUsername(t)); ASSERT_EQ("username", t); - } - - { - DicomInstanceOrigin origin(DicomInstanceOrigin::FromLua()); - - s = 42; - origin.Serialize(s); - } - - { - DicomInstanceOrigin origin(s); - ASSERT_EQ(RequestOrigin_Lua, origin.GetRequestOrigin()); - ASSERT_FALSE(origin.LookupRemoteIp(t)); - ASSERT_FALSE(origin.LookupRemoteAet(t)); - ASSERT_FALSE(origin.LookupCalledAet(t)); - ASSERT_FALSE(origin.LookupHttpUsername(t)); - } - - { - DicomInstanceOrigin origin(DicomInstanceOrigin::FromPlugins()); - - s = 42; - origin.Serialize(s); - } - - { - DicomInstanceOrigin origin(s); - ASSERT_EQ(RequestOrigin_Plugins, origin.GetRequestOrigin()); - ASSERT_FALSE(origin.LookupRemoteIp(t)); - ASSERT_FALSE(origin.LookupRemoteAet(t)); - ASSERT_FALSE(origin.LookupCalledAet(t)); - ASSERT_FALSE(origin.LookupHttpUsername(t)); - } -} - - -namespace -{ - class OrthancJobsSerialization : public testing::Test - { - private: - MemoryStorageArea storage_; - SQLiteDatabaseWrapper db_; // The SQLite DB is in memory - std::unique_ptr context_; - - public: - OrthancJobsSerialization() - { - db_.Open(); - context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10)); - context_->SetupJobsEngine(true, false); - } - - virtual ~OrthancJobsSerialization() ORTHANC_OVERRIDE - { - context_->Stop(); - context_.reset(NULL); - db_.Close(); - } - - ServerContext& GetContext() - { - return *context_; - } - - bool CreateInstance(std::string& id) - { - // Create a sample DICOM file - ParsedDicomFile dicom(true); - dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"), - false, DicomReplaceMode_InsertIfAbsent, ""); - - DicomInstanceToStore toStore; - toStore.SetParsedDicomFile(dicom); - - return (context_->Store(id, toStore, StoreInstanceMode_Default) == StoreStatus_Success); - } - }; -} - - -TEST_F(OrthancJobsSerialization, Values) -{ - std::string id; - ASSERT_TRUE(CreateInstance(id)); - - Json::Value s; - OrthancJobUnserializer unserializer(GetContext()); - - { - DicomInstanceOperationValue instance(GetContext(), id); - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, instance)); - instance.Serialize(s); - } - - std::unique_ptr value; - value.reset(unserializer.UnserializeValue(s)); - ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType()); - ASSERT_EQ(id, dynamic_cast(*value).GetId()); - - { - std::string content; - dynamic_cast(*value).ReadDicom(content); - - ParsedDicomFile dicom(content); - ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME)); - ASSERT_EQ("JODOGNE", content); - } -} - - -TEST_F(OrthancJobsSerialization, Operations) -{ - std::string id; - ASSERT_TRUE(CreateInstance(id)); - - Json::Value s; - OrthancJobUnserializer unserializer(GetContext()); - - // DeleteResourceOperation - - { - DeleteResourceOperation operation(GetContext()); - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); - operation.Serialize(s); - } - - std::unique_ptr operation; - - { - operation.reset(unserializer.UnserializeOperation(s)); - - Json::Value dummy; - ASSERT_THROW(dynamic_cast(*operation).Serialize(dummy), std::bad_cast); - dynamic_cast(*operation).Serialize(dummy); - } - - // StorePeerOperation - - { - WebServiceParameters peer; - peer.SetUrl("http://localhost/"); - peer.SetCredentials("username", "password"); - peer.SetPkcs11Enabled(true); - - StorePeerOperation operation(peer); - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); - operation.Serialize(s); - } - - { - operation.reset(unserializer.UnserializeOperation(s)); - - const StorePeerOperation& tmp = dynamic_cast(*operation); - ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); - ASSERT_EQ("username", tmp.GetPeer().GetUsername()); - ASSERT_EQ("password", tmp.GetPeer().GetPassword()); - ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); - } - - // StoreScuOperation - - { - TimeoutDicomConnectionManager luaManager; - - { - RemoteModalityParameters modality; - modality.SetApplicationEntityTitle("REMOTE"); - modality.SetHost("192.168.1.1"); - modality.SetPortNumber(1000); - modality.SetManufacturer(ModalityManufacturer_StoreScp); - - StoreScuOperation operation(GetContext(), luaManager, "TEST", modality); - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); - operation.Serialize(s); - } - - { - operation.reset(unserializer.UnserializeOperation(s)); - - const StoreScuOperation& tmp = dynamic_cast(*operation); - ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); - ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); - ASSERT_EQ("TEST", tmp.GetLocalAet()); - } - } - - // SystemCallOperation - - { - SystemCallOperation operation(std::string("echo")); - operation.AddPreArgument("a"); - operation.AddPreArgument("b"); - operation.AddPostArgument("c"); - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); - operation.Serialize(s); - } - - { - operation.reset(unserializer.UnserializeOperation(s)); - - const SystemCallOperation& tmp = dynamic_cast(*operation); - ASSERT_EQ("echo", tmp.GetCommand()); - ASSERT_EQ(2u, tmp.GetPreArgumentsCount()); - ASSERT_EQ(1u, tmp.GetPostArgumentsCount()); - ASSERT_EQ("a", tmp.GetPreArgument(0)); - ASSERT_EQ("b", tmp.GetPreArgument(1)); - ASSERT_EQ("c", tmp.GetPostArgument(0)); - } - - // ModifyInstanceOperation - - { - std::unique_ptr modification(new DicomModification); - modification->SetupAnonymization(DicomVersion_2008); - - ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release()); - - ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); - operation.Serialize(s); - } - - { - operation.reset(unserializer.UnserializeOperation(s)); - - const ModifyInstanceOperation& tmp = dynamic_cast(*operation); - ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin()); - ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); - } -} - - -TEST_F(OrthancJobsSerialization, Jobs) -{ - Json::Value s; - - // ArchiveJob - - { - ArchiveJob job(GetContext(), false, false); - ASSERT_FALSE(job.Serialize(s)); // Cannot serialize this - } - - // DicomModalityStoreJob - - OrthancJobUnserializer unserializer(GetContext()); - - { - RemoteModalityParameters modality; - modality.SetApplicationEntityTitle("REMOTE"); - modality.SetHost("192.168.1.1"); - modality.SetPortNumber(1000); - modality.SetManufacturer(ModalityManufacturer_StoreScp); - - DicomModalityStoreJob job(GetContext()); - job.SetLocalAet("LOCAL"); - job.SetRemoteModality(modality); - job.SetMoveOriginator("MOVESCU", 42); - - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - ASSERT_TRUE(job.Serialize(s)); - } - - { - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - DicomModalityStoreJob& tmp = dynamic_cast(*job); - ASSERT_EQ("LOCAL", tmp.GetParameters().GetLocalApplicationEntityTitle()); - ASSERT_EQ("REMOTE", tmp.GetParameters().GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ("192.168.1.1", tmp.GetParameters().GetRemoteModality().GetHost()); - ASSERT_EQ(1000, tmp.GetParameters().GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetParameters().GetRemoteModality().GetManufacturer()); - ASSERT_TRUE(tmp.HasMoveOriginator()); - ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet()); - ASSERT_EQ(42, tmp.GetMoveOriginatorId()); - } - - // OrthancPeerStoreJob - - { - WebServiceParameters peer; - peer.SetUrl("http://localhost/"); - peer.SetCredentials("username", "password"); - peer.SetPkcs11Enabled(true); - - OrthancPeerStoreJob job(GetContext()); - job.SetPeer(peer); - - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - ASSERT_TRUE(job.Serialize(s)); - } - - { - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - OrthancPeerStoreJob& tmp = dynamic_cast(*job); - ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); - ASSERT_EQ("username", tmp.GetPeer().GetUsername()); - ASSERT_EQ("password", tmp.GetPeer().GetPassword()); - ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); - ASSERT_FALSE(tmp.IsTranscode()); - ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException); - } - - { - OrthancPeerStoreJob job(GetContext()); - ASSERT_THROW(job.SetTranscode("nope"), OrthancException); - job.SetTranscode("1.2.840.10008.1.2.4.50"); - - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - ASSERT_TRUE(job.Serialize(s)); - } - - { - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - OrthancPeerStoreJob& tmp = dynamic_cast(*job); - ASSERT_EQ("http://127.0.0.1:8042/", tmp.GetPeer().GetUrl()); - ASSERT_EQ("", tmp.GetPeer().GetUsername()); - ASSERT_EQ("", tmp.GetPeer().GetPassword()); - ASSERT_FALSE(tmp.GetPeer().IsPkcs11Enabled()); - ASSERT_TRUE(tmp.IsTranscode()); - ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax()); - } - - // ResourceModificationJob - - { - std::unique_ptr modification(new DicomModification); - modification->SetupAnonymization(DicomVersion_2008); - - ResourceModificationJob job(GetContext()); - job.SetModification(modification.release(), ResourceType_Patient, true); - job.SetOrigin(DicomInstanceOrigin::FromLua()); - - job.AddTrailingStep(); // Necessary since 1.7.0 - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - ASSERT_TRUE(job.Serialize(s)); - } - - { - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - ResourceModificationJob& tmp = dynamic_cast(*job); - ASSERT_TRUE(tmp.IsAnonymization()); - ASSERT_FALSE(tmp.IsTranscode()); - ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException); - ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); - ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); - } - - { - ResourceModificationJob job(GetContext()); - ASSERT_THROW(job.SetTranscode("nope"), OrthancException); - job.SetTranscode(DicomTransferSyntax_JPEGProcess1); - - job.AddTrailingStep(); // Necessary since 1.7.0 - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - ASSERT_TRUE(job.Serialize(s)); - } - - { - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - ResourceModificationJob& tmp = dynamic_cast(*job); - ASSERT_FALSE(tmp.IsAnonymization()); - ASSERT_TRUE(tmp.IsTranscode()); - ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax()); - ASSERT_EQ(RequestOrigin_Unknown, tmp.GetOrigin().GetRequestOrigin()); - } - - // SplitStudyJob - - std::string instance; - ASSERT_TRUE(CreateInstance(instance)); - - std::string study, series; - - { - ServerContext::DicomCacheLocker lock(GetContext(), instance); - study = lock.GetDicom().GetHasher().HashStudy(); - series = lock.GetDicom().GetHasher().HashSeries(); - } - - { - std::list tmp; - GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); - ASSERT_EQ(1u, tmp.size()); - ASSERT_EQ(study, tmp.front()); - GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); - ASSERT_EQ(1u, tmp.size()); - ASSERT_EQ(series, tmp.front()); - } - - std::string study2; - - { - std::string a, b; - - { - ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); - - SplitStudyJob job(GetContext(), study); - job.SetKeepSource(true); - job.AddSourceSeries(series); - ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); - job.SetOrigin(DicomInstanceOrigin::FromLua()); - job.Replace(DICOM_TAG_PATIENT_NAME, "hello"); - job.Remove(DICOM_TAG_PATIENT_BIRTH_DATE); - ASSERT_THROW(job.Replace(DICOM_TAG_SERIES_DESCRIPTION, "nope"), OrthancException); - ASSERT_THROW(job.Remove(DICOM_TAG_SERIES_DESCRIPTION), OrthancException); - - ASSERT_TRUE(job.GetTargetStudy().empty()); - a = job.GetTargetStudyUid(); - ASSERT_TRUE(job.LookupTargetSeriesUid(b, series)); - - job.AddTrailingStep(); - job.Start(); - ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); - ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); - - study2 = job.GetTargetStudy(); - ASSERT_FALSE(study2.empty()); - - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - ASSERT_TRUE(job.Serialize(s)); - } - - { - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - SplitStudyJob& tmp = dynamic_cast(*job); - ASSERT_TRUE(tmp.IsKeepSource()); - ASSERT_EQ(study, tmp.GetSourceStudy()); - ASSERT_EQ(a, tmp.GetTargetStudyUid()); - ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); - - std::string s; - ASSERT_EQ(study2, tmp.GetTargetStudy()); - ASSERT_FALSE(tmp.LookupTargetSeriesUid(s, "nope")); - ASSERT_TRUE(tmp.LookupTargetSeriesUid(s, series)); - ASSERT_EQ(b, s); - - ASSERT_FALSE(tmp.LookupReplacement(s, DICOM_TAG_STUDY_DESCRIPTION)); - ASSERT_TRUE(tmp.LookupReplacement(s, DICOM_TAG_PATIENT_NAME)); - ASSERT_EQ("hello", s); - ASSERT_FALSE(tmp.IsRemoved(DICOM_TAG_PATIENT_NAME)); - ASSERT_TRUE(tmp.IsRemoved(DICOM_TAG_PATIENT_BIRTH_DATE)); - } - } - - { - std::list tmp; - GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); - ASSERT_EQ(2u, tmp.size()); - GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); - ASSERT_EQ(2u, tmp.size()); - } - - // MergeStudyJob - - { - ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); - - MergeStudyJob job(GetContext(), study); - job.SetKeepSource(true); - job.AddSource(study2); - ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); - ASSERT_THROW(job.AddSourceStudy("nope"), OrthancException); - ASSERT_THROW(job.AddSource("nope"), OrthancException); - job.SetOrigin(DicomInstanceOrigin::FromLua()); - - ASSERT_EQ(job.GetTargetStudy(), study); - - job.AddTrailingStep(); - job.Start(); - ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); - ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); - - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - ASSERT_TRUE(job.Serialize(s)); - } - - { - std::list tmp; - GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); - ASSERT_EQ(2u, tmp.size()); - GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); - ASSERT_EQ(3u, tmp.size()); - } - - { - std::unique_ptr job; - job.reset(unserializer.UnserializeJob(s)); - - MergeStudyJob& tmp = dynamic_cast(*job); - ASSERT_TRUE(tmp.IsKeepSource()); - ASSERT_EQ(study, tmp.GetTargetStudy()); - ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); - } -} - - -TEST(JobsSerialization, Registry) -{ - Json::Value s; - std::string i1, i2; - - { - JobsRegistry registry(10); - registry.Submit(i1, new DummyJob(), 10); - registry.Submit(i2, new SequenceOfOperationsJob(), 30); - registry.Serialize(s); - } - - { - DummyUnserializer unserializer; - JobsRegistry registry(unserializer, s, 10); - - Json::Value t; - registry.Serialize(t); - ASSERT_TRUE(CheckSameJson(s, t)); - } -} - - -TEST(JobsSerialization, TrailingStep) -{ - { - Json::Value s; - - DummyInstancesJob job; - ASSERT_EQ(0u, job.GetCommandsCount()); - ASSERT_EQ(0u, job.GetInstancesCount()); - - job.Start(); - ASSERT_EQ(0u, job.GetPosition()); - ASSERT_FALSE(job.HasTrailingStep()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); - ASSERT_EQ(1u, job.GetPosition()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_THROW(job.Step("jobId"), OrthancException); - } - - { - Json::Value s; - - DummyInstancesJob job; - job.AddInstance("hello"); - job.AddInstance("world"); - ASSERT_EQ(2u, job.GetCommandsCount()); - ASSERT_EQ(2u, job.GetInstancesCount()); - - job.Start(); - ASSERT_EQ(0u, job.GetPosition()); - ASSERT_FALSE(job.HasTrailingStep()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); - ASSERT_EQ(1u, job.GetPosition()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); - ASSERT_EQ(2u, job.GetPosition()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_THROW(job.Step("jobId"), OrthancException); - } - - { - Json::Value s; - - DummyInstancesJob job; - ASSERT_EQ(0u, job.GetInstancesCount()); - ASSERT_EQ(0u, job.GetCommandsCount()); - job.AddTrailingStep(); - ASSERT_EQ(0u, job.GetInstancesCount()); - ASSERT_EQ(1u, job.GetCommandsCount()); - - job.Start(); // This adds the trailing step - ASSERT_EQ(0u, job.GetPosition()); - ASSERT_TRUE(job.HasTrailingStep()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); - ASSERT_EQ(1u, job.GetPosition()); - ASSERT_TRUE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_THROW(job.Step("jobId"), OrthancException); - } - - { - Json::Value s; - - DummyInstancesJob job; - job.AddInstance("hello"); - ASSERT_EQ(1u, job.GetInstancesCount()); - ASSERT_EQ(1u, job.GetCommandsCount()); - job.AddTrailingStep(); - ASSERT_EQ(1u, job.GetInstancesCount()); - ASSERT_EQ(2u, job.GetCommandsCount()); - - job.Start(); - ASSERT_EQ(2u, job.GetCommandsCount()); - ASSERT_EQ(0u, job.GetPosition()); - ASSERT_TRUE(job.HasTrailingStep()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); - ASSERT_EQ(1u, job.GetPosition()); - ASSERT_FALSE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); - ASSERT_EQ(2u, job.GetPosition()); - ASSERT_TRUE(job.IsTrailingStepDone()); - - { - DummyUnserializer unserializer; - ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); - } - - ASSERT_THROW(job.Step("jobId"), OrthancException); - } -} - - -TEST(JobsSerialization, RemoteModalityParameters) -{ - Json::Value s; - - { - RemoteModalityParameters modality; - ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); - modality.Serialize(s, false); - ASSERT_EQ(Json::arrayValue, s.type()); - } - - { - RemoteModalityParameters modality(s); - ASSERT_EQ("ORTHANC", modality.GetApplicationEntityTitle()); - ASSERT_EQ("127.0.0.1", modality.GetHost()); - ASSERT_EQ(104u, modality.GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Generic, modality.GetManufacturer()); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); - ASSERT_TRUE(modality.IsTranscodingAllowed()); - } - - s = Json::nullValue; - - { - RemoteModalityParameters modality; - ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); - ASSERT_THROW(modality.SetPortNumber(0), OrthancException); - ASSERT_THROW(modality.SetPortNumber(65535), OrthancException); - modality.SetApplicationEntityTitle("HELLO"); - modality.SetHost("world"); - modality.SetPortNumber(45); - modality.SetManufacturer(ModalityManufacturer_GenericNoWildcardInDates); - modality.Serialize(s, true); - ASSERT_EQ(Json::objectValue, s.type()); - } - - { - RemoteModalityParameters modality(s); - ASSERT_EQ("HELLO", modality.GetApplicationEntityTitle()); - ASSERT_EQ("world", modality.GetHost()); - ASSERT_EQ(45u, modality.GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_GenericNoWildcardInDates, modality.GetManufacturer()); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); - ASSERT_TRUE(modality.IsTranscodingAllowed()); - } - - s["Port"] = "46"; - - { - RemoteModalityParameters modality(s); - ASSERT_EQ(46u, modality.GetPortNumber()); - } - - s["Port"] = -1; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); - s["Port"] = 65535; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); - s["Port"] = "nope"; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); - - std::set operations; - operations.insert(DicomRequestType_Echo); - operations.insert(DicomRequestType_Find); - operations.insert(DicomRequestType_Get); - operations.insert(DicomRequestType_Move); - operations.insert(DicomRequestType_Store); - operations.insert(DicomRequestType_NAction); - operations.insert(DicomRequestType_NEventReport); - - ASSERT_EQ(7u, operations.size()); - - for (std::set::const_iterator - it = operations.begin(); it != operations.end(); ++it) - { - { - RemoteModalityParameters modality; - modality.SetRequestAllowed(*it, false); - ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); - - modality.Serialize(s, false); - ASSERT_EQ(Json::objectValue, s.type()); - } - - { - RemoteModalityParameters modality(s); - - ASSERT_FALSE(modality.IsRequestAllowed(*it)); - - for (std::set::const_iterator - it2 = operations.begin(); it2 != operations.end(); ++it2) - { - if (*it2 != *it) - { - ASSERT_TRUE(modality.IsRequestAllowed(*it2)); - } - } - } - } - - { - Json::Value s; - s["AllowStorageCommitment"] = false; - s["AET"] = "AET"; - s["Host"] = "host"; - s["Port"] = "104"; - - RemoteModalityParameters modality(s); - ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); - ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); - ASSERT_EQ("host", modality.GetHost()); - ASSERT_EQ(104u, modality.GetPortNumber()); - ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); - ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); - ASSERT_TRUE(modality.IsTranscodingAllowed()); - } - - { - Json::Value s; - s["AllowNAction"] = false; - s["AllowNEventReport"] = true; - s["AET"] = "AET"; - s["Host"] = "host"; - s["Port"] = "104"; - s["AllowTranscoding"] = false; - - RemoteModalityParameters modality(s); - ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); - ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); - ASSERT_EQ("host", modality.GetHost()); - ASSERT_EQ(104u, modality.GetPortNumber()); - ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); - ASSERT_FALSE(modality.IsTranscodingAllowed()); - } - - { - Json::Value s; - s["AllowNAction"] = true; - s["AllowNEventReport"] = true; - s["AET"] = "AET"; - s["Host"] = "host"; - s["Port"] = "104"; - - RemoteModalityParameters modality(s); - ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); - ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); - ASSERT_EQ("host", modality.GetHost()); - ASSERT_EQ(104u, modality.GetPortNumber()); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); - ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); - ASSERT_TRUE(modality.IsTranscodingAllowed()); - } -} - - -TEST_F(OrthancJobsSerialization, DicomAssociationParameters) -{ - Json::Value v; - - { - v = Json::objectValue; - DicomAssociationParameters p; - p.SerializeJob(v); - } - - { - DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); - ASSERT_EQ("ORTHANC", p.GetLocalApplicationEntityTitle()); - ASSERT_EQ("ANY-SCP", p.GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ(104u, p.GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); - ASSERT_EQ("127.0.0.1", p.GetRemoteModality().GetHost()); - ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), p.GetTimeout()); - } - - { - v = Json::objectValue; - DicomAssociationParameters p; - p.SetLocalApplicationEntityTitle("HELLO"); - p.SetRemoteApplicationEntityTitle("WORLD"); - p.SetRemotePort(42); - p.SetRemoteHost("MY_HOST"); - p.SetTimeout(43); - p.SerializeJob(v); - } - - { - DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); - ASSERT_EQ("HELLO", p.GetLocalApplicationEntityTitle()); - ASSERT_EQ("WORLD", p.GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ(42u, p.GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); - ASSERT_EQ("MY_HOST", p.GetRemoteModality().GetHost()); - ASSERT_EQ(43u, p.GetTimeout()); - } - - { - DicomModalityStoreJob job(GetContext()); - job.Serialize(v); - } - - { - OrthancJobUnserializer unserializer(GetContext()); - std::unique_ptr job( - dynamic_cast(unserializer.UnserializeJob(v))); - ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); - ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); - ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); - ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); - ASSERT_FALSE(job->HasMoveOriginator()); - ASSERT_THROW(job->GetMoveOriginatorAet(), OrthancException); - ASSERT_THROW(job->GetMoveOriginatorId(), OrthancException); - ASSERT_FALSE(job->HasStorageCommitment()); - } - - { - RemoteModalityParameters r; - r.SetApplicationEntityTitle("HELLO"); - r.SetPortNumber(42); - r.SetHost("MY_HOST"); - - DicomModalityStoreJob job(GetContext()); - job.SetLocalAet("WORLD"); - job.SetRemoteModality(r); - job.SetTimeout(43); - job.SetMoveOriginator("ORIGINATOR", 100); - job.EnableStorageCommitment(true); - job.Serialize(v); - } - - { - OrthancJobUnserializer unserializer(GetContext()); - std::unique_ptr job( - dynamic_cast(unserializer.UnserializeJob(v))); - ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); - ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); - ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); - ASSERT_EQ(43u, job->GetParameters().GetTimeout()); - ASSERT_TRUE(job->HasMoveOriginator()); - ASSERT_EQ("ORIGINATOR", job->GetMoveOriginatorAet()); - ASSERT_EQ(100, job->GetMoveOriginatorId()); - ASSERT_TRUE(job->HasStorageCommitment()); - } - - { - DicomMoveScuJob job(GetContext()); - job.Serialize(v); - } - - { - OrthancJobUnserializer unserializer(GetContext()); - std::unique_ptr job( - dynamic_cast(unserializer.UnserializeJob(v))); - ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); - ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); - ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); - ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); - } - - { - RemoteModalityParameters r; - r.SetApplicationEntityTitle("HELLO"); - r.SetPortNumber(42); - r.SetHost("MY_HOST"); - - DicomMoveScuJob job(GetContext()); - job.SetLocalAet("WORLD"); - job.SetRemoteModality(r); - job.SetTimeout(43); - job.Serialize(v); - } - - { - OrthancJobUnserializer unserializer(GetContext()); - std::unique_ptr job( - dynamic_cast(unserializer.UnserializeJob(v))); - ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); - ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); - ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); - ASSERT_EQ(43u, job->GetParameters().GetTimeout()); - } -} diff -r e241e5f3f088 -r 149172a06b4d OrthancServer/UnitTestsSources/ServerJobsTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Thu Jun 11 14:29:14 2020 +0200 @@ -0,0 +1,1099 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../../OrthancFramework/Sources/Compatibility.h" +#include "../../OrthancFramework/Sources/FileStorage/MemoryStorageArea.h" +#include "../../OrthancFramework/Sources/SerializationToolbox.h" + +#include "../Sources/Database/SQLiteDatabaseWrapper.h" +#include "../Sources/ServerContext.h" +#include "../Sources/ServerJobs/LuaJobManager.h" +#include "../Sources/ServerJobs/OrthancJobUnserializer.h" + +#include "../../OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h" +#include "../Sources/ServerJobs/Operations/DicomInstanceOperationValue.h" +#include "../Sources/ServerJobs/Operations/DeleteResourceOperation.h" +#include "../Sources/ServerJobs/Operations/ModifyInstanceOperation.h" +#include "../Sources/ServerJobs/Operations/StorePeerOperation.h" +#include "../Sources/ServerJobs/Operations/StoreScuOperation.h" +#include "../Sources/ServerJobs/Operations/SystemCallOperation.h" + +#include "../Sources/ServerJobs/ArchiveJob.h" +#include "../Sources/ServerJobs/DicomModalityStoreJob.h" +#include "../Sources/ServerJobs/DicomMoveScuJob.h" +#include "../Sources/ServerJobs/MergeStudyJob.h" +#include "../Sources/ServerJobs/OrthancPeerStoreJob.h" +#include "../Sources/ServerJobs/ResourceModificationJob.h" +#include "../Sources/ServerJobs/SplitStudyJob.h" + + +using namespace Orthanc; + +namespace +{ + class DummyJob : public IJob + { + private: + bool fails_; + unsigned int count_; + unsigned int steps_; + + public: + DummyJob() : + fails_(false), + count_(0), + steps_(4) + { + } + + explicit DummyJob(bool fails) : + fails_(fails), + count_(0), + steps_(4) + { + } + + virtual void Start() ORTHANC_OVERRIDE + { + } + + virtual void Reset() ORTHANC_OVERRIDE + { + } + + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE + { + if (fails_) + { + return JobStepResult::Failure(ErrorCode_ParameterOutOfRange, NULL); + } + else if (count_ == steps_ - 1) + { + return JobStepResult::Success(); + } + else + { + count_++; + return JobStepResult::Continue(); + } + } + + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE + { + } + + virtual float GetProgress() ORTHANC_OVERRIDE + { + return static_cast(count_) / static_cast(steps_ - 1); + } + + virtual void GetJobType(std::string& type) ORTHANC_OVERRIDE + { + type = "DummyJob"; + } + + virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE + { + value = Json::objectValue; + value["Type"] = "DummyJob"; + return true; + } + + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE + { + value["hello"] = "world"; + } + + virtual bool GetOutput(std::string& output, + MimeType& mime, + const std::string& key) ORTHANC_OVERRIDE + { + return false; + } + }; + + + class DummyInstancesJob : public SetOfInstancesJob + { + private: + bool trailingStepDone_; + + protected: + virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE + { + return (instance != "nope"); + } + + virtual bool HandleTrailingStep() ORTHANC_OVERRIDE + { + if (HasTrailingStep()) + { + if (trailingStepDone_) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + trailingStepDone_ = true; + return true; + } + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + public: + DummyInstancesJob() : + trailingStepDone_(false) + { + } + + DummyInstancesJob(const Json::Value& value) : + SetOfInstancesJob(value) + { + if (HasTrailingStep()) + { + trailingStepDone_ = (GetPosition() == GetCommandsCount()); + } + else + { + trailingStepDone_ = false; + } + } + + bool IsTrailingStepDone() const + { + return trailingStepDone_; + } + + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE + { + } + + virtual void GetJobType(std::string& s) ORTHANC_OVERRIDE + { + s = "DummyInstancesJob"; + } + }; + + + class DummyUnserializer : public GenericJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& value) ORTHANC_OVERRIDE + { + if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob") + { + return new DummyInstancesJob(value); + } + else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob") + { + return new DummyJob; + } + else + { + return GenericJobUnserializer::UnserializeJob(value); + } + } + }; + + + class DynamicInteger : public IDynamicObject + { + private: + int value_; + std::set& target_; + + public: + DynamicInteger(int value, std::set& target) : + value_(value), target_(target) + { + } + + int GetValue() const + { + return value_; + } + }; +} + + +TEST(JobsEngine, DISABLED_Lua) +{ + JobsEngine engine(10); + engine.SetThreadSleep(10); + engine.SetWorkersCount(2); + engine.Start(); + + LuaJobManager lua; + lua.SetMaxOperationsPerJob(5); + lua.SetTrailingOperationTimeout(200); + + for (size_t i = 0; i < 30; i++) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(150)); + + LuaJobManager::Lock lock(lua, engine); + size_t a = lock.AddLogOperation(); + size_t b = lock.AddLogOperation(); + size_t c = lock.AddSystemCallOperation("echo"); + lock.AddStringInput(a, boost::lexical_cast(i)); + lock.AddNullInput(a); + lock.Connect(a, b); + lock.Connect(a, c); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + + engine.Stop(); +} + + +static bool CheckSameJson(const Json::Value& a, + const Json::Value& b) +{ + std::string s = a.toStyledString(); + std::string t = b.toStyledString(); + + if (s == t) + { + return true; + } + else + { + LOG(ERROR) << "Expected serialization: " << s; + LOG(ERROR) << "Actual serialization: " << t; + return false; + } +} + + +static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer, + SetOfInstancesJob& job) +{ + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::unique_ptr unserialized + (dynamic_cast(unserializer.UnserializeJob(a))); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return (CheckSameJson(a, b) && + job.HasTrailingStep() == unserialized->HasTrailingStep() && + job.GetPosition() == unserialized->GetPosition() && + job.GetInstancesCount() == unserialized->GetInstancesCount() && + job.GetCommandsCount() == unserialized->GetCommandsCount()); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJobOperation& operation) +{ + Json::Value a = 42; + operation.Serialize(a); + + std::unique_ptr unserialized(unserializer.UnserializeOperation(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + JobOperationValue& value) +{ + Json::Value a = 42; + value.Serialize(a); + + std::unique_ptr unserialized(unserializer.UnserializeValue(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + + +TEST(JobsSerialization, GenericOperations) +{ + DummyUnserializer unserializer; + Json::Value s; + + { + LogJobOperation operation; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + + { + std::unique_ptr operation; + operation.reset(unserializer.UnserializeOperation(s)); + + // Make sure that we have indeed unserialized a log operation + Json::Value dummy; + ASSERT_THROW(dynamic_cast(*operation).Serialize(dummy), std::bad_cast); + dynamic_cast(*operation).Serialize(dummy); + } +} + + +TEST(JobsSerialization, DicomInstanceOrigin) +{ + Json::Value s; + std::string t; + + { + DicomInstanceOrigin origin; + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Unknown, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromDicomProtocol("host", "aet", "called")); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_DicomProtocol, origin.GetRequestOrigin()); + ASSERT_EQ("aet", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_TRUE(origin.LookupRemoteAet(t)); ASSERT_EQ("aet", t); + ASSERT_TRUE(origin.LookupCalledAet(t)); ASSERT_EQ("called", t); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromHttp("host", "username")); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_RestApi, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_TRUE(origin.LookupHttpUsername(t)); ASSERT_EQ("username", t); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromLua()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Lua, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromPlugins()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Plugins, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } +} + + +namespace +{ + class OrthancJobsSerialization : public testing::Test + { + private: + MemoryStorageArea storage_; + SQLiteDatabaseWrapper db_; // The SQLite DB is in memory + std::unique_ptr context_; + + public: + OrthancJobsSerialization() + { + db_.Open(); + context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10)); + context_->SetupJobsEngine(true, false); + } + + virtual ~OrthancJobsSerialization() ORTHANC_OVERRIDE + { + context_->Stop(); + context_.reset(NULL); + db_.Close(); + } + + ServerContext& GetContext() + { + return *context_; + } + + bool CreateInstance(std::string& id) + { + // Create a sample DICOM file + ParsedDicomFile dicom(true); + dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"), + false, DicomReplaceMode_InsertIfAbsent, ""); + + DicomInstanceToStore toStore; + toStore.SetParsedDicomFile(dicom); + + return (context_->Store(id, toStore, StoreInstanceMode_Default) == StoreStatus_Success); + } + }; +} + + +TEST_F(OrthancJobsSerialization, Values) +{ + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + { + DicomInstanceOperationValue instance(GetContext(), id); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, instance)); + instance.Serialize(s); + } + + std::unique_ptr value; + value.reset(unserializer.UnserializeValue(s)); + ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType()); + ASSERT_EQ(id, dynamic_cast(*value).GetId()); + + { + std::string content; + dynamic_cast(*value).ReadDicom(content); + + ParsedDicomFile dicom(content); + ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("JODOGNE", content); + } +} + + +TEST_F(OrthancJobsSerialization, Operations) +{ + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + // DeleteResourceOperation + + { + DeleteResourceOperation operation(GetContext()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + std::unique_ptr operation; + + { + operation.reset(unserializer.UnserializeOperation(s)); + + Json::Value dummy; + ASSERT_THROW(dynamic_cast(*operation).Serialize(dummy), std::bad_cast); + dynamic_cast(*operation).Serialize(dummy); + } + + // StorePeerOperation + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetCredentials("username", "password"); + peer.SetPkcs11Enabled(true); + + StorePeerOperation operation(peer); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const StorePeerOperation& tmp = dynamic_cast(*operation); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + } + + // StoreScuOperation + + { + TimeoutDicomConnectionManager luaManager; + + { + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + StoreScuOperation operation(GetContext(), luaManager, "TEST", modality); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const StoreScuOperation& tmp = dynamic_cast(*operation); + ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("TEST", tmp.GetLocalAet()); + } + } + + // SystemCallOperation + + { + SystemCallOperation operation(std::string("echo")); + operation.AddPreArgument("a"); + operation.AddPreArgument("b"); + operation.AddPostArgument("c"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const SystemCallOperation& tmp = dynamic_cast(*operation); + ASSERT_EQ("echo", tmp.GetCommand()); + ASSERT_EQ(2u, tmp.GetPreArgumentsCount()); + ASSERT_EQ(1u, tmp.GetPostArgumentsCount()); + ASSERT_EQ("a", tmp.GetPreArgument(0)); + ASSERT_EQ("b", tmp.GetPreArgument(1)); + ASSERT_EQ("c", tmp.GetPostArgument(0)); + } + + // ModifyInstanceOperation + + { + std::unique_ptr modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const ModifyInstanceOperation& tmp = dynamic_cast(*operation); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } +} + + +TEST_F(OrthancJobsSerialization, Jobs) +{ + Json::Value s; + + // ArchiveJob + + { + ArchiveJob job(GetContext(), false, false); + ASSERT_FALSE(job.Serialize(s)); // Cannot serialize this + } + + // DicomModalityStoreJob + + OrthancJobUnserializer unserializer(GetContext()); + + { + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + DicomModalityStoreJob job(GetContext()); + job.SetLocalAet("LOCAL"); + job.SetRemoteModality(modality); + job.SetMoveOriginator("MOVESCU", 42); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + DicomModalityStoreJob& tmp = dynamic_cast(*job); + ASSERT_EQ("LOCAL", tmp.GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("REMOTE", tmp.GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_TRUE(tmp.HasMoveOriginator()); + ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet()); + ASSERT_EQ(42, tmp.GetMoveOriginatorId()); + } + + // OrthancPeerStoreJob + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetCredentials("username", "password"); + peer.SetPkcs11Enabled(true); + + OrthancPeerStoreJob job(GetContext()); + job.SetPeer(peer); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + OrthancPeerStoreJob& tmp = dynamic_cast(*job); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + ASSERT_FALSE(tmp.IsTranscode()); + ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException); + } + + { + OrthancPeerStoreJob job(GetContext()); + ASSERT_THROW(job.SetTranscode("nope"), OrthancException); + job.SetTranscode("1.2.840.10008.1.2.4.50"); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + OrthancPeerStoreJob& tmp = dynamic_cast(*job); + ASSERT_EQ("http://127.0.0.1:8042/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("", tmp.GetPeer().GetUsername()); + ASSERT_EQ("", tmp.GetPeer().GetPassword()); + ASSERT_FALSE(tmp.GetPeer().IsPkcs11Enabled()); + ASSERT_TRUE(tmp.IsTranscode()); + ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax()); + } + + // ResourceModificationJob + + { + std::unique_ptr modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ResourceModificationJob job(GetContext()); + job.SetModification(modification.release(), ResourceType_Patient, true); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + job.AddTrailingStep(); // Necessary since 1.7.0 + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + ResourceModificationJob& tmp = dynamic_cast(*job); + ASSERT_TRUE(tmp.IsAnonymization()); + ASSERT_FALSE(tmp.IsTranscode()); + ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } + + { + ResourceModificationJob job(GetContext()); + ASSERT_THROW(job.SetTranscode("nope"), OrthancException); + job.SetTranscode(DicomTransferSyntax_JPEGProcess1); + + job.AddTrailingStep(); // Necessary since 1.7.0 + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + ResourceModificationJob& tmp = dynamic_cast(*job); + ASSERT_FALSE(tmp.IsAnonymization()); + ASSERT_TRUE(tmp.IsTranscode()); + ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax()); + ASSERT_EQ(RequestOrigin_Unknown, tmp.GetOrigin().GetRequestOrigin()); + } + + // SplitStudyJob + + std::string instance; + ASSERT_TRUE(CreateInstance(instance)); + + std::string study, series; + + { + ServerContext::DicomCacheLocker lock(GetContext(), instance); + study = lock.GetDicom().GetHasher().HashStudy(); + series = lock.GetDicom().GetHasher().HashSeries(); + } + + { + std::list tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(1u, tmp.size()); + ASSERT_EQ(study, tmp.front()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(1u, tmp.size()); + ASSERT_EQ(series, tmp.front()); + } + + std::string study2; + + { + std::string a, b; + + { + ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); + + SplitStudyJob job(GetContext(), study); + job.SetKeepSource(true); + job.AddSourceSeries(series); + ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + job.Replace(DICOM_TAG_PATIENT_NAME, "hello"); + job.Remove(DICOM_TAG_PATIENT_BIRTH_DATE); + ASSERT_THROW(job.Replace(DICOM_TAG_SERIES_DESCRIPTION, "nope"), OrthancException); + ASSERT_THROW(job.Remove(DICOM_TAG_SERIES_DESCRIPTION), OrthancException); + + ASSERT_TRUE(job.GetTargetStudy().empty()); + a = job.GetTargetStudyUid(); + ASSERT_TRUE(job.LookupTargetSeriesUid(b, series)); + + job.AddTrailingStep(); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + + study2 = job.GetTargetStudy(); + ASSERT_FALSE(study2.empty()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + SplitStudyJob& tmp = dynamic_cast(*job); + ASSERT_TRUE(tmp.IsKeepSource()); + ASSERT_EQ(study, tmp.GetSourceStudy()); + ASSERT_EQ(a, tmp.GetTargetStudyUid()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + + std::string s; + ASSERT_EQ(study2, tmp.GetTargetStudy()); + ASSERT_FALSE(tmp.LookupTargetSeriesUid(s, "nope")); + ASSERT_TRUE(tmp.LookupTargetSeriesUid(s, series)); + ASSERT_EQ(b, s); + + ASSERT_FALSE(tmp.LookupReplacement(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(tmp.LookupReplacement(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("hello", s); + ASSERT_FALSE(tmp.IsRemoved(DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(tmp.IsRemoved(DICOM_TAG_PATIENT_BIRTH_DATE)); + } + } + + { + std::list tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(2u, tmp.size()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(2u, tmp.size()); + } + + // MergeStudyJob + + { + ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); + + MergeStudyJob job(GetContext(), study); + job.SetKeepSource(true); + job.AddSource(study2); + ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); + ASSERT_THROW(job.AddSourceStudy("nope"), OrthancException); + ASSERT_THROW(job.AddSource("nope"), OrthancException); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + ASSERT_EQ(job.GetTargetStudy(), study); + + job.AddTrailingStep(); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::list tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(2u, tmp.size()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(3u, tmp.size()); + } + + { + std::unique_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + MergeStudyJob& tmp = dynamic_cast(*job); + ASSERT_TRUE(tmp.IsKeepSource()); + ASSERT_EQ(study, tmp.GetTargetStudy()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + } +} + + + +TEST_F(OrthancJobsSerialization, DicomAssociationParameters) +{ + Json::Value v; + + { + v = Json::objectValue; + DicomAssociationParameters p; + p.SerializeJob(v); + } + + { + DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); + ASSERT_EQ("ORTHANC", p.GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", p.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ(104u, p.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("127.0.0.1", p.GetRemoteModality().GetHost()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), p.GetTimeout()); + } + + { + v = Json::objectValue; + DicomAssociationParameters p; + p.SetLocalApplicationEntityTitle("HELLO"); + p.SetRemoteApplicationEntityTitle("WORLD"); + p.SetRemotePort(42); + p.SetRemoteHost("MY_HOST"); + p.SetTimeout(43); + p.SerializeJob(v); + } + + { + DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); + ASSERT_EQ("HELLO", p.GetLocalApplicationEntityTitle()); + ASSERT_EQ("WORLD", p.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ(42u, p.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("MY_HOST", p.GetRemoteModality().GetHost()); + ASSERT_EQ(43u, p.GetTimeout()); + } + + { + DicomModalityStoreJob job(GetContext()); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); + ASSERT_FALSE(job->HasMoveOriginator()); + ASSERT_THROW(job->GetMoveOriginatorAet(), OrthancException); + ASSERT_THROW(job->GetMoveOriginatorId(), OrthancException); + ASSERT_FALSE(job->HasStorageCommitment()); + } + + { + RemoteModalityParameters r; + r.SetApplicationEntityTitle("HELLO"); + r.SetPortNumber(42); + r.SetHost("MY_HOST"); + + DicomModalityStoreJob job(GetContext()); + job.SetLocalAet("WORLD"); + job.SetRemoteModality(r); + job.SetTimeout(43); + job.SetMoveOriginator("ORIGINATOR", 100); + job.EnableStorageCommitment(true); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(43u, job->GetParameters().GetTimeout()); + ASSERT_TRUE(job->HasMoveOriginator()); + ASSERT_EQ("ORIGINATOR", job->GetMoveOriginatorAet()); + ASSERT_EQ(100, job->GetMoveOriginatorId()); + ASSERT_TRUE(job->HasStorageCommitment()); + } + + { + DicomMoveScuJob job(GetContext()); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); + } + + { + RemoteModalityParameters r; + r.SetApplicationEntityTitle("HELLO"); + r.SetPortNumber(42); + r.SetHost("MY_HOST"); + + DicomMoveScuJob job(GetContext()); + job.SetLocalAet("WORLD"); + job.SetRemoteModality(r); + job.SetTimeout(43); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(43u, job->GetParameters().GetTimeout()); + } +}