changeset 4059:e241e5f3f088 framework

moved LuaTests and MemoryCacheTests from OrthancServer to OrthancFramework
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 11 Jun 2020 14:12:07 +0200
parents 2a8bf0991be0
children 149172a06b4d
files CMakeLists.txt OrthancFramework/UnitTestsSources/LuaTests.cpp OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp OrthancServer/UnitTestsSources/LuaServerTests.cpp OrthancServer/UnitTestsSources/LuaTests.cpp OrthancServer/UnitTestsSources/MemoryCacheTests.cpp OrthancServer/UnitTestsSources/UnitTestsMain.cpp
diffstat 7 files changed, 878 insertions(+), 841 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Jun 11 13:57:44 2020 +0200
+++ b/CMakeLists.txt	Thu Jun 11 14:12:07 2020 +0200
@@ -122,6 +122,8 @@
   OrthancFramework/UnitTestsSources/ImageTests.cpp
   OrthancFramework/UnitTestsSources/JpegLosslessTests.cpp
   OrthancFramework/UnitTestsSources/LoggingTests.cpp
+  OrthancFramework/UnitTestsSources/LuaTests.cpp
+  OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp
   OrthancFramework/UnitTestsSources/RestApiTests.cpp
   OrthancFramework/UnitTestsSources/SQLiteChromiumTests.cpp
   OrthancFramework/UnitTestsSources/SQLiteTests.cpp
@@ -132,9 +134,9 @@
 
 set(ORTHANC_SERVER_UNIT_TESTS
   OrthancServer/UnitTestsSources/DatabaseLookupTests.cpp
-  OrthancServer/UnitTestsSources/LuaTests.cpp
-  OrthancServer/UnitTestsSources/MemoryCacheTests.cpp
+  OrthancServer/UnitTestsSources/LuaServerTests.cpp
   OrthancServer/UnitTestsSources/MultiThreadingTests.cpp
+  OrthancServer/UnitTestsSources/PluginsTests.cpp
   OrthancServer/UnitTestsSources/ServerIndexTests.cpp
   OrthancServer/UnitTestsSources/UnitTestsMain.cpp
   OrthancServer/UnitTestsSources/VersionsTests.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/UnitTestsSources/LuaTests.cpp	Thu Jun 11 14:12:07 2020 +0200
@@ -0,0 +1,193 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+#  include <OrthancFramework.h>
+#endif
+
+#include "gtest/gtest.h"
+
+#include "../Sources/OrthancException.h"
+#include "../Sources/Toolbox.h"
+#include "../Sources/Lua/LuaFunctionCall.h"
+
+#include <boost/lexical_cast.hpp>
+
+
+TEST(Lua, Existing)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute("a={}");
+  lua.Execute("function f() end");
+
+  ASSERT_TRUE(lua.IsExistingFunction("f"));
+  ASSERT_FALSE(lua.IsExistingFunction("a"));
+  ASSERT_FALSE(lua.IsExistingFunction("Dummy"));
+}
+
+
+TEST(Lua, ReturnJson)
+{
+  Json::Value b = Json::objectValue;
+  b["a"] = 42;
+  b["b"] = 44.37;
+  b["c"] = -43;
+
+  Json::Value c = Json::arrayValue;
+  c.append("test3");
+  c.append("test1");
+  c.append("test2");
+
+  Json::Value a = Json::objectValue;
+  a["Hello"] = "World";
+  a["List"] = Json::arrayValue;
+  a["List"].append(b);
+  a["List"].append(c);
+
+  Orthanc::LuaContext lua;
+
+  // This is the identity function (it simply returns its input)
+  lua.Execute("function identity(a) return a end");
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson("hello");
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    ASSERT_EQ("hello", v.asString());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(42.25);
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    ASSERT_FLOAT_EQ(42.25f, v.asFloat());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(-42);
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    ASSERT_EQ(-42, v.asInt());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    Json::Value vv = Json::arrayValue;
+    f.PushJson(vv);
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    ASSERT_EQ(Json::arrayValue, v.type());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    Json::Value vv = Json::objectValue;
+    f.PushJson(vv);
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    // Lua does not make the distinction between empty lists and empty objects
+    ASSERT_EQ(Json::arrayValue, v.type());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(b);
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    ASSERT_EQ(Json::objectValue, v.type());
+    ASSERT_FLOAT_EQ(42.0f, v["a"].asFloat());
+    ASSERT_FLOAT_EQ(44.37f, v["b"].asFloat());
+    ASSERT_FLOAT_EQ(-43.0f, v["c"].asFloat());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(c);
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    ASSERT_EQ(Json::arrayValue, v.type());
+    ASSERT_EQ("test3", v[0].asString());
+    ASSERT_EQ("test1", v[1].asString());
+    ASSERT_EQ("test2", v[2].asString());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(a);
+    Json::Value v;
+    f.ExecuteToJson(v, false);
+    ASSERT_EQ("World", v["Hello"].asString());
+    ASSERT_EQ(Json::intValue, v["List"][0]["a"].type());
+    ASSERT_EQ(Json::realValue, v["List"][0]["b"].type());
+    ASSERT_EQ(Json::intValue, v["List"][0]["c"].type());
+    ASSERT_EQ(42, v["List"][0]["a"].asInt());
+    ASSERT_FLOAT_EQ(44.37f, v["List"][0]["b"].asFloat());
+    ASSERT_EQ(44, v["List"][0]["b"].asInt());
+    ASSERT_EQ(-43, v["List"][0]["c"].asInt());
+    ASSERT_EQ("test3", v["List"][1][0].asString());
+    ASSERT_EQ("test1", v["List"][1][1].asString());
+    ASSERT_EQ("test2", v["List"][1][2].asString());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(a);
+    Json::Value v;
+    f.ExecuteToJson(v, true);
+    ASSERT_EQ("World", v["Hello"].asString());
+    ASSERT_EQ(Json::stringValue, v["List"][0]["a"].type());
+    ASSERT_EQ(Json::stringValue, v["List"][0]["b"].type());
+    ASSERT_EQ(Json::stringValue, v["List"][0]["c"].type());
+    ASSERT_FLOAT_EQ(42.0f, boost::lexical_cast<float>(v["List"][0]["a"].asString()));
+    ASSERT_FLOAT_EQ(44.37f, boost::lexical_cast<float>(v["List"][0]["b"].asString()));
+    ASSERT_FLOAT_EQ(-43.0f, boost::lexical_cast<float>(v["List"][0]["c"].asString()));
+    ASSERT_EQ("test3", v["List"][1][0].asString());
+    ASSERT_EQ("test1", v["List"][1][1].asString());
+    ASSERT_EQ("test2", v["List"][1][2].asString());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "DumpJson");
+    f.PushJson(a);
+    std::string s;
+    f.ExecuteToString(s);
+
+    Json::FastWriter writer;
+    std::string t = writer.write(a);
+
+    ASSERT_EQ(s, t);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp	Thu Jun 11 14:12:07 2020 +0200
@@ -0,0 +1,371 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+#  include <OrthancFramework.h>
+#endif
+
+#include "gtest/gtest.h"
+
+#include <memory>
+#include <algorithm>
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "../Sources/Cache/MemoryCache.h"
+#include "../Sources/Cache/MemoryStringCache.h"
+#include "../Sources/Cache/SharedArchive.h"
+#include "../Sources/IDynamicObject.h"
+#include "../Sources/Logging.h"
+
+
+TEST(LRU, Basic)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string> r;
+  
+  r.Add("d");
+  r.Add("a");
+  r.Add("c");
+  r.Add("b");
+
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ("a", r.RemoveOldest());
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ("b", r.RemoveOldest());
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ("d", r.RemoveOldest());
+  ASSERT_EQ("c", r.GetOldest());
+  ASSERT_EQ("c", r.RemoveOldest());
+
+  ASSERT_TRUE(r.IsEmpty());
+
+  ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException);
+  ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException);
+}
+
+
+TEST(LRU, Payload)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("c", 422);
+  r.Add("d", 423);
+
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
+
+  ASSERT_TRUE(r.Contains("b"));
+  ASSERT_EQ(421, r.Invalidate("b"));
+  ASSERT_FALSE(r.Contains("b"));
+
+  int p;
+  ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p);
+  ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
+  ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(420, r.GetOldestPayload());
+  ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(423, r.GetOldestPayload());
+  ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("c", r.GetOldest());
+  ASSERT_EQ(422, r.GetOldestPayload());
+  ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+TEST(LRU, PayloadUpdate)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("d", 423);
+
+  r.MakeMostRecent("a", 424);
+  r.MakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+
+TEST(LRU, PayloadUpdateBis)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.AddOrMakeMostRecent("a", 420);
+  r.AddOrMakeMostRecent("b", 421);
+  r.AddOrMakeMostRecent("d", 423);
+  r.AddOrMakeMostRecent("a", 424);
+  r.AddOrMakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+TEST(LRU, GetAllKeys)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  std::vector<std::string> keys;
+
+  r.AddOrMakeMostRecent("a", 420);
+  r.GetAllKeys(keys);
+
+  ASSERT_EQ(1u, keys.size());
+  ASSERT_EQ("a", keys[0]);
+
+  r.AddOrMakeMostRecent("b", 421);
+  r.GetAllKeys(keys);
+
+  ASSERT_EQ(2u, keys.size());
+  ASSERT_TRUE(std::find(keys.begin(), keys.end(),"a") != keys.end());
+  ASSERT_TRUE(std::find(keys.begin(), keys.end(),"b") != keys.end());
+}
+
+
+
+namespace
+{
+  class Integer : public Orthanc::IDynamicObject
+  {
+  private:
+    std::string& log_;
+    int value_;
+
+  public:
+    Integer(std::string& log, int v) : log_(log), value_(v)
+    {
+    }
+
+    virtual ~Integer() ORTHANC_OVERRIDE
+    {
+      LOG(INFO) << "Removing cache entry for " << value_;
+      log_ += boost::lexical_cast<std::string>(value_) + " ";
+    }
+  };
+
+  class IntegerProvider : public Orthanc::Deprecated::ICachePageProvider
+  {
+  public:
+    std::string log_;
+
+    virtual Orthanc::IDynamicObject* Provide(const std::string& s) ORTHANC_OVERRIDE
+    {
+      LOG(INFO) << "Providing " << s;
+      return new Integer(log_, boost::lexical_cast<int>(s));
+    }
+  };
+}
+
+
+TEST(MemoryCache, Basic)
+{
+  IntegerProvider provider;
+
+  {
+    Orthanc::Deprecated::MemoryCache cache(provider, 3);
+    cache.Access("42");  // 42 -> exit
+    cache.Access("43");  // 43, 42 -> exit
+    cache.Access("45");  // 45, 43, 42 -> exit
+    cache.Access("42");  // 42, 45, 43 -> exit
+    cache.Access("43");  // 43, 42, 45 -> exit
+    cache.Access("47");  // 45 is removed; 47, 43, 42 -> exit 
+    cache.Access("44");  // 42 is removed; 44, 47, 43 -> exit
+    cache.Access("42");  // 43 is removed; 42, 44, 47 -> exit
+    // Closing the cache: 47, 44, 42 are successively removed
+  }
+
+  ASSERT_EQ("45 42 43 47 44 42 ", provider.log_);
+}
+
+
+
+
+
+namespace
+{
+  class S : public Orthanc::IDynamicObject
+  {
+  private:
+    std::string value_;
+
+  public:
+    S(const std::string& value) : value_(value)
+    {
+    }
+
+    const std::string& GetValue() const
+    {
+      return value_;
+    }
+  };
+}
+
+
+TEST(LRU, SharedArchive)
+{
+  std::string first, second;
+  Orthanc::SharedArchive a(3);
+  first = a.Add(new S("First item"));
+  second = a.Add(new S("Second item"));
+
+  for (int i = 1; i < 100; i++)
+  {
+    a.Add(new S("Item " + boost::lexical_cast<std::string>(i)));
+    
+    // Continuously protect the two first items
+    {
+      Orthanc::SharedArchive::Accessor accessor(a, first);
+      ASSERT_TRUE(accessor.IsValid());
+      ASSERT_EQ("First item", dynamic_cast<S&>(accessor.GetItem()).GetValue());
+    }
+
+    {
+      Orthanc::SharedArchive::Accessor accessor(a, second);
+      ASSERT_TRUE(accessor.IsValid());
+      ASSERT_EQ("Second item", dynamic_cast<S&>(accessor.GetItem()).GetValue());
+    }
+
+    {
+      Orthanc::SharedArchive::Accessor accessor(a, "nope");
+      ASSERT_FALSE(accessor.IsValid());
+      ASSERT_THROW(accessor.GetItem(), Orthanc::OrthancException);
+    }
+  }
+
+  std::list<std::string> i;
+  a.List(i);
+
+  size_t count = 0;
+  for (std::list<std::string>::const_iterator
+         it = i.begin(); it != i.end(); it++)
+  {
+    if (*it == first ||
+        *it == second)
+    {
+      count++;
+    }
+  }
+
+  ASSERT_EQ(2u, count);
+}
+
+
+TEST(MemoryStringCache, Basic)
+{
+  Orthanc::MemoryStringCache c;
+  ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException);
+  
+  c.SetMaximumSize(2);
+
+  std::string v;
+  ASSERT_FALSE(c.Fetch(v, "hello"));
+
+  c.Add("hello", "a");
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_FALSE(c.Fetch(v, "hello2"));
+  ASSERT_FALSE(c.Fetch(v, "hello3"));
+
+  c.Add("hello2", "b");
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+  ASSERT_FALSE(c.Fetch(v, "hello3"));
+
+  c.Add("hello3", "too large value");
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+  ASSERT_FALSE(c.Fetch(v, "hello3"));
+  
+  c.Add("hello3", "c");
+  ASSERT_FALSE(c.Fetch(v, "hello"));  // Recycled
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+  ASSERT_TRUE(c.Fetch(v, "hello3"));  ASSERT_EQ("c", v);
+}
+
+
+TEST(MemoryStringCache, Invalidate)
+{
+  Orthanc::MemoryStringCache c;
+  c.Add("hello", "a");
+  c.Add("hello2", "b");
+
+  std::string v;
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+
+  c.Invalidate("hello");
+  ASSERT_FALSE(c.Fetch(v, "hello"));
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/UnitTestsSources/LuaServerTests.cpp	Thu Jun 11 14:12:07 2020 +0200
@@ -0,0 +1,208 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../../OrthancFramework/Sources/OrthancException.h"
+#include "../../OrthancFramework/Sources/Toolbox.h"
+#include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
+
+#include <OrthancServerResources.h>
+
+#include <boost/lexical_cast.hpp>
+
+#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS)
+#  error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS to 0 or 1"
+#endif
+
+
+TEST(Lua, Json)
+{
+  Orthanc::LuaContext lua;
+
+  {
+    std::string command;
+    Orthanc::ServerResources::GetFileResource(command, Orthanc::ServerResources::LUA_TOOLBOX);
+    lua.Execute(command);
+  }
+
+  lua.Execute("a={}");
+  lua.Execute("a['x'] = 10");
+  lua.Execute("a['y'] = {}");
+  lua.Execute("a['y'][1] = 20");
+  lua.Execute("a['y'][2] = 20");
+  lua.Execute("PrintRecursive(a)");
+  lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end");
+
+  Json::Value v, vv, o;
+  //v["a"] = "b";
+  v.append("hello");
+  v.append("world");
+  v.append("42");
+  vv.append("sub");
+  vv.append("set");
+  v.append(vv);
+  o = Json::objectValue;
+  o["x"] = 10;
+  o["y"] = 20;
+  o["z"] = 20.5f;
+  v.append(o);
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushJson(v);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJson(o);
+    ASSERT_THROW(f.ExecutePredicate(), Orthanc::OrthancException);
+  }
+
+  o["bool"] = false;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJson(o);
+    ASSERT_FALSE(f.ExecutePredicate());
+  }
+
+  o["bool"] = true;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJson(o);
+    ASSERT_TRUE(f.ExecutePredicate());
+  }
+}
+
+
+TEST(Lua, Simple)
+{
+  Orthanc::LuaContext lua;
+
+  {
+    std::string command;
+    Orthanc::ServerResources::GetFileResource(command, Orthanc::ServerResources::LUA_TOOLBOX);
+    lua.Execute(command);
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushString("hello");
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushBoolean(true);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushInteger(42);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushDouble(3.1415);
+    f.Execute();
+  }
+}
+
+
+TEST(Lua, Http)
+{
+  Orthanc::LuaContext lua;
+
+#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
+  // The "http://www.orthanc-server.com/downloads/third-party/" does
+  // not automatically redirect to HTTPS, so we cas use it even if the
+  // OpenSSL/HTTPS support is disabled in curl
+  const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/";
+
+#if LUA_VERSION_NUM >= 502
+  // Since Lua >= 5.2.0, the function "loadstring" has been replaced by "load"
+  lua.Execute("JSON = load(HttpGet('" + BASE + "JSON.lua')) ()");
+#else
+  lua.Execute("JSON = loadstring(HttpGet('" + BASE + "JSON.lua')) ()");
+#endif
+
+  const std::string url(BASE + "Product.json");
+#endif
+
+  std::string s;
+  lua.Execute(s, "print(HttpGet({}))");
+  ASSERT_EQ("nil", Orthanc::Toolbox::StripSpaces(s));
+
+#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1  
+  lua.Execute(s, "print(string.len(HttpGet(\"" + url + "\")))");
+  ASSERT_LE(100, boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(s)));
+
+  // Parse a JSON file
+  lua.Execute(s, "print(JSON:decode(HttpGet(\"" + url + "\")) ['Product'])");
+  ASSERT_EQ("OrthancClient", Orthanc::Toolbox::StripSpaces(s));
+
+#if 0
+  // This part of the test can only be executed if one instance of
+  // Orthanc is running on the localhost
+
+  lua.Execute("modality = {}");
+  lua.Execute("table.insert(modality, 'ORTHANC')");
+  lua.Execute("table.insert(modality, 'localhost')");
+  lua.Execute("table.insert(modality, 4242)");
+  
+  lua.Execute(s, "print(HttpPost(\"http://localhost:8042/tools/execute-script\", \"print('hello world')\"))");
+  ASSERT_EQ("hello world", Orthanc::Toolbox::StripSpaces(s));
+
+  lua.Execute(s, "print(JSON:decode(HttpPost(\"http://localhost:8042/tools/execute-script\", \"print('[10,42,1000]')\")) [2])");
+  ASSERT_EQ("42", Orthanc::Toolbox::StripSpaces(s));
+
+  // Add/remove a modality with Lua
+  Json::Value v;
+  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities/lua'))");
+  ASSERT_EQ(0, Orthanc::Toolbox::StripSpaces(s).size());
+  lua.Execute(s, "print(HttpPut('http://localhost:8042/modalities/lua', JSON:encode(modality)))");
+  lua.Execute(v, "print(HttpGet('http://localhost:8042/modalities/lua'))");
+  ASSERT_TRUE(v.type() == Json::arrayValue);
+  lua.Execute(s, "print(HttpDelete('http://localhost:8042/modalities/lua'))");
+  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities/lua'))");
+  ASSERT_EQ(0, Orthanc::Toolbox::StripSpaces(s).size());
+#endif
+
+#endif
+}
--- a/OrthancServer/UnitTestsSources/LuaTests.cpp	Thu Jun 11 13:57:44 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,372 +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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
-#  include <OrthancFramework.h>
-#endif
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../../OrthancFramework/Sources/OrthancException.h"
-#include "../../OrthancFramework/Sources/Toolbox.h"
-#include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
-
-#include <OrthancServerResources.h>
-
-#include <boost/lexical_cast.hpp>
-
-#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS)
-#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS to 0 or 1"
-#endif
-
-
-TEST(Lua, Json)
-{
-  Orthanc::LuaContext lua;
-
-#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK != 1
-  {
-    std::string command;
-    Orthanc::ServerResources::GetFileResource(command, Orthanc::ServerResources::LUA_TOOLBOX);
-    lua.Execute(command);
-  }
-#endif
-
-  lua.Execute("a={}");
-  lua.Execute("a['x'] = 10");
-  lua.Execute("a['y'] = {}");
-  lua.Execute("a['y'][1] = 20");
-  lua.Execute("a['y'][2] = 20");
-
-#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK != 1
-  lua.Execute("PrintRecursive(a)");
-#endif
-
-  lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end");
-
-  Json::Value v, vv, o;
-  //v["a"] = "b";
-  v.append("hello");
-  v.append("world");
-  v.append("42");
-  vv.append("sub");
-  vv.append("set");
-  v.append(vv);
-  o = Json::objectValue;
-  o["x"] = 10;
-  o["y"] = 20;
-  o["z"] = 20.5f;
-  v.append(o);
-
-#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK != 1
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushJson(v);
-    f.Execute();
-  }
-#endif
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "f");
-    f.PushJson(o);
-    ASSERT_THROW(f.ExecutePredicate(), Orthanc::OrthancException);
-  }
-
-  o["bool"] = false;
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "f");
-    f.PushJson(o);
-    ASSERT_FALSE(f.ExecutePredicate());
-  }
-
-  o["bool"] = true;
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "f");
-    f.PushJson(o);
-    ASSERT_TRUE(f.ExecutePredicate());
-  }
-}
-
-
-TEST(Lua, Existing)
-{
-  Orthanc::LuaContext lua;
-  lua.Execute("a={}");
-  lua.Execute("function f() end");
-
-  ASSERT_TRUE(lua.IsExistingFunction("f"));
-  ASSERT_FALSE(lua.IsExistingFunction("a"));
-  ASSERT_FALSE(lua.IsExistingFunction("Dummy"));
-}
-
-
-#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK != 1
-TEST(Lua, Simple)
-{
-  Orthanc::LuaContext lua;
-
-  {
-    std::string command;
-    Orthanc::ServerResources::GetFileResource(command, Orthanc::ServerResources::LUA_TOOLBOX);
-    lua.Execute(command);
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushString("hello");
-    f.Execute();
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushBoolean(true);
-    f.Execute();
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushInteger(42);
-    f.Execute();
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushDouble(3.1415);
-    f.Execute();
-  }
-}
-#endif
-
-
-TEST(Lua, ReturnJson)
-{
-  Json::Value b = Json::objectValue;
-  b["a"] = 42;
-  b["b"] = 44.37;
-  b["c"] = -43;
-
-  Json::Value c = Json::arrayValue;
-  c.append("test3");
-  c.append("test1");
-  c.append("test2");
-
-  Json::Value a = Json::objectValue;
-  a["Hello"] = "World";
-  a["List"] = Json::arrayValue;
-  a["List"].append(b);
-  a["List"].append(c);
-
-  Orthanc::LuaContext lua;
-
-  // This is the identity function (it simply returns its input)
-  lua.Execute("function identity(a) return a end");
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    f.PushJson("hello");
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    ASSERT_EQ("hello", v.asString());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    f.PushJson(42.25);
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    ASSERT_FLOAT_EQ(42.25f, v.asFloat());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    f.PushJson(-42);
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    ASSERT_EQ(-42, v.asInt());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    Json::Value vv = Json::arrayValue;
-    f.PushJson(vv);
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    ASSERT_EQ(Json::arrayValue, v.type());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    Json::Value vv = Json::objectValue;
-    f.PushJson(vv);
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    // Lua does not make the distinction between empty lists and empty objects
-    ASSERT_EQ(Json::arrayValue, v.type());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    f.PushJson(b);
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    ASSERT_EQ(Json::objectValue, v.type());
-    ASSERT_FLOAT_EQ(42.0f, v["a"].asFloat());
-    ASSERT_FLOAT_EQ(44.37f, v["b"].asFloat());
-    ASSERT_FLOAT_EQ(-43.0f, v["c"].asFloat());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    f.PushJson(c);
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    ASSERT_EQ(Json::arrayValue, v.type());
-    ASSERT_EQ("test3", v[0].asString());
-    ASSERT_EQ("test1", v[1].asString());
-    ASSERT_EQ("test2", v[2].asString());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    f.PushJson(a);
-    Json::Value v;
-    f.ExecuteToJson(v, false);
-    ASSERT_EQ("World", v["Hello"].asString());
-    ASSERT_EQ(Json::intValue, v["List"][0]["a"].type());
-    ASSERT_EQ(Json::realValue, v["List"][0]["b"].type());
-    ASSERT_EQ(Json::intValue, v["List"][0]["c"].type());
-    ASSERT_EQ(42, v["List"][0]["a"].asInt());
-    ASSERT_FLOAT_EQ(44.37f, v["List"][0]["b"].asFloat());
-    ASSERT_EQ(44, v["List"][0]["b"].asInt());
-    ASSERT_EQ(-43, v["List"][0]["c"].asInt());
-    ASSERT_EQ("test3", v["List"][1][0].asString());
-    ASSERT_EQ("test1", v["List"][1][1].asString());
-    ASSERT_EQ("test2", v["List"][1][2].asString());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "identity");
-    f.PushJson(a);
-    Json::Value v;
-    f.ExecuteToJson(v, true);
-    ASSERT_EQ("World", v["Hello"].asString());
-    ASSERT_EQ(Json::stringValue, v["List"][0]["a"].type());
-    ASSERT_EQ(Json::stringValue, v["List"][0]["b"].type());
-    ASSERT_EQ(Json::stringValue, v["List"][0]["c"].type());
-    ASSERT_FLOAT_EQ(42.0f, boost::lexical_cast<float>(v["List"][0]["a"].asString()));
-    ASSERT_FLOAT_EQ(44.37f, boost::lexical_cast<float>(v["List"][0]["b"].asString()));
-    ASSERT_FLOAT_EQ(-43.0f, boost::lexical_cast<float>(v["List"][0]["c"].asString()));
-    ASSERT_EQ("test3", v["List"][1][0].asString());
-    ASSERT_EQ("test1", v["List"][1][1].asString());
-    ASSERT_EQ("test2", v["List"][1][2].asString());
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "DumpJson");
-    f.PushJson(a);
-    std::string s;
-    f.ExecuteToString(s);
-
-    Json::FastWriter writer;
-    std::string t = writer.write(a);
-
-    ASSERT_EQ(s, t);
-  }
-}
-
-
-
-TEST(Lua, Http)
-{
-  Orthanc::LuaContext lua;
-
-#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
-  // The "http://www.orthanc-server.com/downloads/third-party/" does
-  // not automatically redirect to HTTPS, so we cas use it even if the
-  // OpenSSL/HTTPS support is disabled in curl
-  const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/";
-
-#if LUA_VERSION_NUM >= 502
-  // Since Lua >= 5.2.0, the function "loadstring" has been replaced by "load"
-  lua.Execute("JSON = load(HttpGet('" + BASE + "JSON.lua')) ()");
-#else
-  lua.Execute("JSON = loadstring(HttpGet('" + BASE + "JSON.lua')) ()");
-#endif
-
-  const std::string url(BASE + "Product.json");
-#endif
-
-  std::string s;
-  lua.Execute(s, "print(HttpGet({}))");
-  ASSERT_EQ("nil", Orthanc::Toolbox::StripSpaces(s));
-
-#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1  
-  lua.Execute(s, "print(string.len(HttpGet(\"" + url + "\")))");
-  ASSERT_LE(100, boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(s)));
-
-  // Parse a JSON file
-  lua.Execute(s, "print(JSON:decode(HttpGet(\"" + url + "\")) ['Product'])");
-  ASSERT_EQ("OrthancClient", Orthanc::Toolbox::StripSpaces(s));
-
-#if 0
-  // This part of the test can only be executed if one instance of
-  // Orthanc is running on the localhost
-
-  lua.Execute("modality = {}");
-  lua.Execute("table.insert(modality, 'ORTHANC')");
-  lua.Execute("table.insert(modality, 'localhost')");
-  lua.Execute("table.insert(modality, 4242)");
-  
-  lua.Execute(s, "print(HttpPost(\"http://localhost:8042/tools/execute-script\", \"print('hello world')\"))");
-  ASSERT_EQ("hello world", Orthanc::Toolbox::StripSpaces(s));
-
-  lua.Execute(s, "print(JSON:decode(HttpPost(\"http://localhost:8042/tools/execute-script\", \"print('[10,42,1000]')\")) [2])");
-  ASSERT_EQ("42", Orthanc::Toolbox::StripSpaces(s));
-
-  // Add/remove a modality with Lua
-  Json::Value v;
-  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities/lua'))");
-  ASSERT_EQ(0, Orthanc::Toolbox::StripSpaces(s).size());
-  lua.Execute(s, "print(HttpPut('http://localhost:8042/modalities/lua', JSON:encode(modality)))");
-  lua.Execute(v, "print(HttpGet('http://localhost:8042/modalities/lua'))");
-  ASSERT_TRUE(v.type() == Json::arrayValue);
-  lua.Execute(s, "print(HttpDelete('http://localhost:8042/modalities/lua'))");
-  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities/lua'))");
-  ASSERT_EQ(0, Orthanc::Toolbox::StripSpaces(s).size());
-#endif
-
-#endif
-}
--- a/OrthancServer/UnitTestsSources/MemoryCacheTests.cpp	Thu Jun 11 13:57:44 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,460 +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 <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include <memory>
-#include <algorithm>
-#include <boost/thread.hpp>
-#include <boost/lexical_cast.hpp>
-
-#include "../../OrthancFramework/Sources/Cache/MemoryCache.h"
-#include "../../OrthancFramework/Sources/Cache/MemoryStringCache.h"
-#include "../../OrthancFramework/Sources/Cache/SharedArchive.h"
-#include "../../OrthancFramework/Sources/IDynamicObject.h"
-#include "../../OrthancFramework/Sources/Logging.h"
-#include "../Sources/StorageCommitmentReports.h"
-
-
-TEST(LRU, Basic)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string> r;
-  
-  r.Add("d");
-  r.Add("a");
-  r.Add("c");
-  r.Add("b");
-
-  r.MakeMostRecent("a");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("b");
-  r.MakeMostRecent("c");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("c");
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ("a", r.RemoveOldest());
-  ASSERT_EQ("b", r.GetOldest());
-  ASSERT_EQ("b", r.RemoveOldest());
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ("d", r.RemoveOldest());
-  ASSERT_EQ("c", r.GetOldest());
-  ASSERT_EQ("c", r.RemoveOldest());
-
-  ASSERT_TRUE(r.IsEmpty());
-
-  ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException);
-  ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException);
-}
-
-
-TEST(LRU, Payload)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
-  
-  r.Add("a", 420);
-  r.Add("b", 421);
-  r.Add("c", 422);
-  r.Add("d", 423);
-
-  r.MakeMostRecent("a");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("b");
-  r.MakeMostRecent("c");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("c");
-
-  ASSERT_TRUE(r.Contains("b"));
-  ASSERT_EQ(421, r.Invalidate("b"));
-  ASSERT_FALSE(r.Contains("b"));
-
-  int p;
-  ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p);
-  ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
-  ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ(420, r.GetOldestPayload());
-  ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
-
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ(423, r.GetOldestPayload());
-  ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
-
-  ASSERT_EQ("c", r.GetOldest());
-  ASSERT_EQ(422, r.GetOldestPayload());
-  ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
-
-  ASSERT_TRUE(r.IsEmpty());
-}
-
-
-TEST(LRU, PayloadUpdate)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
-  
-  r.Add("a", 420);
-  r.Add("b", 421);
-  r.Add("d", 423);
-
-  r.MakeMostRecent("a", 424);
-  r.MakeMostRecent("d", 421);
-
-  ASSERT_EQ("b", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ(424, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_TRUE(r.IsEmpty());
-}
-
-
-
-TEST(LRU, PayloadUpdateBis)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
-  
-  r.AddOrMakeMostRecent("a", 420);
-  r.AddOrMakeMostRecent("b", 421);
-  r.AddOrMakeMostRecent("d", 423);
-  r.AddOrMakeMostRecent("a", 424);
-  r.AddOrMakeMostRecent("d", 421);
-
-  ASSERT_EQ("b", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ(424, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_TRUE(r.IsEmpty());
-}
-
-TEST(LRU, GetAllKeys)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
-  std::vector<std::string> keys;
-
-  r.AddOrMakeMostRecent("a", 420);
-  r.GetAllKeys(keys);
-
-  ASSERT_EQ(1u, keys.size());
-  ASSERT_EQ("a", keys[0]);
-
-  r.AddOrMakeMostRecent("b", 421);
-  r.GetAllKeys(keys);
-
-  ASSERT_EQ(2u, keys.size());
-  ASSERT_TRUE(std::find(keys.begin(), keys.end(),"a") != keys.end());
-  ASSERT_TRUE(std::find(keys.begin(), keys.end(),"b") != keys.end());
-}
-
-
-
-namespace
-{
-  class Integer : public Orthanc::IDynamicObject
-  {
-  private:
-    std::string& log_;
-    int value_;
-
-  public:
-    Integer(std::string& log, int v) : log_(log), value_(v)
-    {
-    }
-
-    virtual ~Integer() ORTHANC_OVERRIDE
-    {
-      LOG(INFO) << "Removing cache entry for " << value_;
-      log_ += boost::lexical_cast<std::string>(value_) + " ";
-    }
-  };
-
-  class IntegerProvider : public Orthanc::Deprecated::ICachePageProvider
-  {
-  public:
-    std::string log_;
-
-    virtual Orthanc::IDynamicObject* Provide(const std::string& s) ORTHANC_OVERRIDE
-    {
-      LOG(INFO) << "Providing " << s;
-      return new Integer(log_, boost::lexical_cast<int>(s));
-    }
-  };
-}
-
-
-TEST(MemoryCache, Basic)
-{
-  IntegerProvider provider;
-
-  {
-    Orthanc::Deprecated::MemoryCache cache(provider, 3);
-    cache.Access("42");  // 42 -> exit
-    cache.Access("43");  // 43, 42 -> exit
-    cache.Access("45");  // 45, 43, 42 -> exit
-    cache.Access("42");  // 42, 45, 43 -> exit
-    cache.Access("43");  // 43, 42, 45 -> exit
-    cache.Access("47");  // 45 is removed; 47, 43, 42 -> exit 
-    cache.Access("44");  // 42 is removed; 44, 47, 43 -> exit
-    cache.Access("42");  // 43 is removed; 42, 44, 47 -> exit
-    // Closing the cache: 47, 44, 42 are successively removed
-  }
-
-  ASSERT_EQ("45 42 43 47 44 42 ", provider.log_);
-}
-
-
-
-
-
-namespace
-{
-  class S : public Orthanc::IDynamicObject
-  {
-  private:
-    std::string value_;
-
-  public:
-    S(const std::string& value) : value_(value)
-    {
-    }
-
-    const std::string& GetValue() const
-    {
-      return value_;
-    }
-  };
-}
-
-
-TEST(LRU, SharedArchive)
-{
-  std::string first, second;
-  Orthanc::SharedArchive a(3);
-  first = a.Add(new S("First item"));
-  second = a.Add(new S("Second item"));
-
-  for (int i = 1; i < 100; i++)
-  {
-    a.Add(new S("Item " + boost::lexical_cast<std::string>(i)));
-    
-    // Continuously protect the two first items
-    {
-      Orthanc::SharedArchive::Accessor accessor(a, first);
-      ASSERT_TRUE(accessor.IsValid());
-      ASSERT_EQ("First item", dynamic_cast<S&>(accessor.GetItem()).GetValue());
-    }
-
-    {
-      Orthanc::SharedArchive::Accessor accessor(a, second);
-      ASSERT_TRUE(accessor.IsValid());
-      ASSERT_EQ("Second item", dynamic_cast<S&>(accessor.GetItem()).GetValue());
-    }
-
-    {
-      Orthanc::SharedArchive::Accessor accessor(a, "nope");
-      ASSERT_FALSE(accessor.IsValid());
-      ASSERT_THROW(accessor.GetItem(), Orthanc::OrthancException);
-    }
-  }
-
-  std::list<std::string> i;
-  a.List(i);
-
-  size_t count = 0;
-  for (std::list<std::string>::const_iterator
-         it = i.begin(); it != i.end(); it++)
-  {
-    if (*it == first ||
-        *it == second)
-    {
-      count++;
-    }
-  }
-
-  ASSERT_EQ(2u, count);
-}
-
-
-TEST(MemoryStringCache, Basic)
-{
-  Orthanc::MemoryStringCache c;
-  ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException);
-  
-  c.SetMaximumSize(2);
-
-  std::string v;
-  ASSERT_FALSE(c.Fetch(v, "hello"));
-
-  c.Add("hello", "a");
-  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
-  ASSERT_FALSE(c.Fetch(v, "hello2"));
-  ASSERT_FALSE(c.Fetch(v, "hello3"));
-
-  c.Add("hello2", "b");
-  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
-  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
-  ASSERT_FALSE(c.Fetch(v, "hello3"));
-
-  c.Add("hello3", "too large value");
-  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
-  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
-  ASSERT_FALSE(c.Fetch(v, "hello3"));
-  
-  c.Add("hello3", "c");
-  ASSERT_FALSE(c.Fetch(v, "hello"));  // Recycled
-  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
-  ASSERT_TRUE(c.Fetch(v, "hello3"));  ASSERT_EQ("c", v);
-}
-
-
-TEST(MemoryStringCache, Invalidate)
-{
-  Orthanc::MemoryStringCache c;
-  c.Add("hello", "a");
-  c.Add("hello2", "b");
-
-  std::string v;
-  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
-  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
-
-  c.Invalidate("hello");
-  ASSERT_FALSE(c.Fetch(v, "hello"));
-  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
-}
-
-
-TEST(StorageCommitmentReports, Basic)
-{
-  Orthanc::StorageCommitmentReports reports(2);
-  ASSERT_EQ(2u, reports.GetMaxSize());
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "nope");
-    ASSERT_EQ("nope", accessor.GetTransactionUid());
-    ASSERT_FALSE(accessor.IsValid());
-    ASSERT_THROW(accessor.GetReport(), Orthanc::OrthancException);
-  }
-
-  reports.Store("a", new Orthanc::StorageCommitmentReports::Report("aet_a"));
-  reports.Store("b", new Orthanc::StorageCommitmentReports::Report("aet_b"));
-  reports.Store("c", new Orthanc::StorageCommitmentReports::Report("aet_c"));
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
-    ASSERT_FALSE(accessor.IsValid());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
-    ASSERT_TRUE(accessor.IsValid());
-    ASSERT_EQ("aet_b", accessor.GetReport().GetRemoteAet());
-    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Pending,
-              accessor.GetReport().GetStatus());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
-    ASSERT_EQ("aet_c", accessor.GetReport().GetRemoteAet());
-    ASSERT_TRUE(accessor.IsValid());
-  }
-
-  {
-    std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report
-      (new Orthanc::StorageCommitmentReports::Report("aet"));
-    report->AddSuccess("class1", "instance1");
-    report->AddFailure("class2", "instance2",
-                       Orthanc::StorageCommitmentFailureReason_ReferencedSOPClassNotSupported);
-    report->MarkAsComplete();
-    reports.Store("a", report.release());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
-    ASSERT_TRUE(accessor.IsValid());
-    ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet());
-    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Failure,
-              accessor.GetReport().GetStatus());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
-    ASSERT_FALSE(accessor.IsValid());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
-    ASSERT_TRUE(accessor.IsValid());
-  }
-
-  {
-    std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report
-      (new Orthanc::StorageCommitmentReports::Report("aet"));
-    report->AddSuccess("class1", "instance1");
-    report->MarkAsComplete();
-    reports.Store("a", report.release());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
-    ASSERT_TRUE(accessor.IsValid());
-    ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet());
-    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Success,
-              accessor.GetReport().GetStatus());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
-    ASSERT_FALSE(accessor.IsValid());
-  }
-
-  {
-    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
-    ASSERT_TRUE(accessor.IsValid());
-  }
-}
--- a/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Thu Jun 11 13:57:44 2020 +0200
+++ b/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Thu Jun 11 14:12:07 2020 +0200
@@ -32,18 +32,20 @@
 
 
 #include "PrecompiledHeadersUnitTests.h"
-#include "../../OrthancFramework/Sources/EnumerationDictionary.h"
 
 #include "gtest/gtest.h"
 
-#include "../../OrthancFramework/Sources/Logging.h"
-#include "../../OrthancFramework/Sources/Toolbox.h"
-#include "../../OrthancFramework/Sources/OrthancException.h"
+#include <OrthancServerResources.h>
+
+#include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
+#include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
+#include "../../OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.h"
+#include "../../OrthancFramework/Sources/EnumerationDictionary.h"
 #include "../../OrthancFramework/Sources/Images/Image.h"
 #include "../../OrthancFramework/Sources/Images/PngWriter.h"
-#include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
-#include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
-#include "../../OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.h"
+#include "../../OrthancFramework/Sources/Logging.h"
+#include "../../OrthancFramework/Sources/OrthancException.h"
+#include "../../OrthancFramework/Sources/Toolbox.h"
 
 #include "../Plugins/Engine/PluginsEnumerations.h"
 #include "../Sources/DicomInstanceToStore.h"
@@ -51,6 +53,7 @@
 #include "../Sources/OrthancInitialization.h"
 #include "../Sources/ServerEnumerations.h"
 #include "../Sources/ServerToolbox.h"
+#include "../Sources/StorageCommitmentReports.h"
 
 #include <dcmtk/dcmdata/dcdeftag.h>
 
@@ -411,6 +414,98 @@
 }
 
 
+TEST(StorageCommitmentReports, Basic)
+{
+  Orthanc::StorageCommitmentReports reports(2);
+  ASSERT_EQ(2u, reports.GetMaxSize());
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "nope");
+    ASSERT_EQ("nope", accessor.GetTransactionUid());
+    ASSERT_FALSE(accessor.IsValid());
+    ASSERT_THROW(accessor.GetReport(), Orthanc::OrthancException);
+  }
+
+  reports.Store("a", new Orthanc::StorageCommitmentReports::Report("aet_a"));
+  reports.Store("b", new Orthanc::StorageCommitmentReports::Report("aet_b"));
+  reports.Store("c", new Orthanc::StorageCommitmentReports::Report("aet_c"));
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
+    ASSERT_FALSE(accessor.IsValid());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
+    ASSERT_TRUE(accessor.IsValid());
+    ASSERT_EQ("aet_b", accessor.GetReport().GetRemoteAet());
+    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Pending,
+              accessor.GetReport().GetStatus());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
+    ASSERT_EQ("aet_c", accessor.GetReport().GetRemoteAet());
+    ASSERT_TRUE(accessor.IsValid());
+  }
+
+  {
+    std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report
+      (new Orthanc::StorageCommitmentReports::Report("aet"));
+    report->AddSuccess("class1", "instance1");
+    report->AddFailure("class2", "instance2",
+                       Orthanc::StorageCommitmentFailureReason_ReferencedSOPClassNotSupported);
+    report->MarkAsComplete();
+    reports.Store("a", report.release());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
+    ASSERT_TRUE(accessor.IsValid());
+    ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet());
+    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Failure,
+              accessor.GetReport().GetStatus());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
+    ASSERT_FALSE(accessor.IsValid());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
+    ASSERT_TRUE(accessor.IsValid());
+  }
+
+  {
+    std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report
+      (new Orthanc::StorageCommitmentReports::Report("aet"));
+    report->AddSuccess("class1", "instance1");
+    report->MarkAsComplete();
+    reports.Store("a", report.release());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
+    ASSERT_TRUE(accessor.IsValid());
+    ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet());
+    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Success,
+              accessor.GetReport().GetStatus());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
+    ASSERT_FALSE(accessor.IsValid());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
+    ASSERT_TRUE(accessor.IsValid());
+  }
+}
+
+
+
 int main(int argc, char **argv)
 {
   Logging::Initialize();