282
|
1 #include "gtest/gtest.h"
|
|
2
|
283
|
3 #include <glog/logging.h>
|
282
|
4 #include <memory>
|
283
|
5 #include <boost/thread.hpp>
|
|
6 #include <boost/lexical_cast.hpp>
|
282
|
7 #include "../Core/IDynamicObject.h"
|
|
8 #include "../Core/MultiThreading/CacheIndex.h"
|
|
9
|
|
10
|
|
11 TEST(CacheIndex, Basic)
|
|
12 {
|
|
13 Orthanc::CacheIndex<std::string> r;
|
|
14
|
|
15 r.Add("d");
|
|
16 r.Add("a");
|
|
17 r.Add("c");
|
|
18 r.Add("b");
|
|
19
|
|
20 r.TagAsMostRecent("a");
|
|
21 r.TagAsMostRecent("d");
|
|
22 r.TagAsMostRecent("b");
|
|
23 r.TagAsMostRecent("c");
|
|
24 r.TagAsMostRecent("d");
|
|
25 r.TagAsMostRecent("c");
|
|
26
|
|
27 ASSERT_EQ("a", r.RemoveOldest());
|
|
28 ASSERT_EQ("b", r.RemoveOldest());
|
|
29 ASSERT_EQ("d", r.RemoveOldest());
|
|
30 ASSERT_EQ("c", r.RemoveOldest());
|
|
31
|
|
32 ASSERT_TRUE(r.IsEmpty());
|
|
33 }
|
|
34
|
|
35
|
|
36 TEST(CacheIndex, Payload)
|
|
37 {
|
|
38 Orthanc::CacheIndex<std::string, int> r;
|
|
39
|
|
40 r.Add("a", 420);
|
|
41 r.Add("b", 421);
|
|
42 r.Add("c", 422);
|
|
43 r.Add("d", 423);
|
|
44
|
|
45 r.TagAsMostRecent("a");
|
|
46 r.TagAsMostRecent("d");
|
|
47 r.TagAsMostRecent("b");
|
|
48 r.TagAsMostRecent("c");
|
|
49 r.TagAsMostRecent("d");
|
|
50 r.TagAsMostRecent("c");
|
|
51
|
|
52 ASSERT_TRUE(r.Contains("b"));
|
|
53 ASSERT_EQ(421, r.Invalidate("b"));
|
|
54 ASSERT_FALSE(r.Contains("b"));
|
|
55
|
|
56 int p;
|
283
|
57 ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p);
|
|
58 ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
|
|
59 ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
|
|
60
|
282
|
61 ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
|
|
62 ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
|
|
63 ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
|
|
64
|
|
65 ASSERT_TRUE(r.IsEmpty());
|
|
66 }
|
|
67
|
|
68
|
|
69 namespace Orthanc
|
|
70 {
|
283
|
71
|
|
72 class ICacheProvider
|
|
73 {
|
|
74 public:
|
|
75 virtual ~ICacheProvider()
|
|
76 {
|
|
77 }
|
|
78
|
|
79 virtual IDynamicObject* Provide(const std::string& id) = 0;
|
|
80 };
|
|
81
|
282
|
82 class MemoryCache
|
|
83 {
|
|
84 private:
|
283
|
85 struct Page
|
282
|
86 {
|
|
87 std::string id_;
|
283
|
88 std::auto_ptr<IDynamicObject> content_;
|
|
89 };
|
|
90
|
|
91 ICacheProvider& provider_;
|
|
92 size_t cacheSize_;
|
|
93 CacheIndex<std::string, Page*> index_;
|
|
94
|
|
95 Page& Load(const std::string& id)
|
|
96 {
|
|
97 // Reuse the cache entry if it already exists
|
|
98 Page* p = NULL;
|
|
99 if (index_.Contains(id, p))
|
|
100 {
|
|
101 assert(p != NULL);
|
|
102 index_.TagAsMostRecent(id);
|
|
103 return *p;
|
|
104 }
|
|
105
|
|
106 // The id is not in the cache yet. Make some room if the cache
|
|
107 // is full.
|
|
108 if (index_.GetSize() == cacheSize_)
|
|
109 {
|
|
110 index_.RemoveOldest(p);
|
|
111 delete p;
|
|
112 }
|
|
113
|
|
114 // Create a new cache page
|
|
115 std::auto_ptr<Page> result(new Page);
|
|
116 result->id_ = id;
|
|
117 result->content_.reset(provider_.Provide(id));
|
|
118
|
|
119 // Add the newly create page to the cache
|
|
120 p = result.release();
|
|
121 index_.Add(id, p);
|
|
122 return *p;
|
|
123 }
|
|
124
|
|
125 public:
|
|
126 class Accessor
|
|
127 {
|
|
128 friend class MemoryCache;
|
|
129
|
|
130 private:
|
|
131 Page& element_;
|
|
132
|
|
133 Accessor(Page& element) :
|
|
134 element_(element)
|
|
135 {
|
|
136 }
|
|
137
|
|
138 public:
|
|
139 const std::string GetId() const
|
|
140 {
|
|
141 return element_.id_;
|
|
142 }
|
|
143
|
|
144 IDynamicObject& GetContent()
|
|
145 {
|
|
146 return *element_.content_;
|
|
147 }
|
|
148
|
|
149 const IDynamicObject& GetContent() const
|
|
150 {
|
|
151 return *element_.content_;
|
|
152 }
|
282
|
153 };
|
|
154
|
283
|
155 MemoryCache(ICacheProvider& provider,
|
|
156 size_t cacheSize) :
|
|
157 provider_(provider),
|
|
158 cacheSize_(cacheSize)
|
|
159 {
|
|
160 }
|
|
161
|
|
162 ~MemoryCache()
|
|
163 {
|
|
164 while (!index_.IsEmpty())
|
|
165 {
|
|
166 Page* element = NULL;
|
|
167 index_.RemoveOldest(element);
|
|
168 assert(element != NULL);
|
|
169 delete element;
|
|
170 }
|
|
171 }
|
282
|
172
|
283
|
173 Accessor* Access(const std::string& id)
|
|
174 {
|
|
175 Page& element = Load(id);
|
|
176 return new Accessor(element);
|
|
177 }
|
|
178 };
|
|
179 }
|
|
180
|
|
181
|
|
182
|
|
183 namespace
|
|
184 {
|
|
185 class Integer : public Orthanc::IDynamicObject
|
|
186 {
|
|
187 private:
|
|
188 std::string& log_;
|
|
189 int value_;
|
282
|
190
|
|
191 public:
|
283
|
192 Integer(std::string& log, int v) : log_(log), value_(v)
|
|
193 {
|
|
194 }
|
|
195
|
|
196 virtual ~Integer()
|
|
197 {
|
|
198 LOG(INFO) << "Removing cache entry for " << value_;
|
|
199 log_ += boost::lexical_cast<std::string>(value_) + " ";
|
|
200 }
|
|
201
|
|
202 int GetValue() const
|
|
203 {
|
|
204 return value_;
|
|
205 }
|
|
206 };
|
|
207
|
|
208 class IntegerProvider : public Orthanc::ICacheProvider
|
|
209 {
|
|
210 public:
|
|
211 std::string log_;
|
|
212
|
|
213 Orthanc::IDynamicObject* Provide(const std::string& s)
|
|
214 {
|
|
215 LOG(INFO) << "Providing " << s;
|
|
216 return new Integer(log_, boost::lexical_cast<int>(s));
|
|
217 }
|
282
|
218 };
|
|
219 }
|
283
|
220
|
|
221
|
|
222 TEST(MemoryCache, Basic)
|
|
223 {
|
|
224 IntegerProvider provider;
|
|
225
|
|
226 {
|
|
227 Orthanc::MemoryCache cache(provider, 3);
|
|
228 std::auto_ptr<Orthanc::MemoryCache::Accessor> a;
|
|
229 a.reset(cache.Access("42")); // 42 -> exit
|
|
230 a.reset(cache.Access("43")); // 43, 42 -> exit
|
|
231 a.reset(cache.Access("45")); // 45, 43, 42 -> exit
|
|
232 a.reset(cache.Access("42")); // 42, 45, 43 -> exit
|
|
233 a.reset(cache.Access("43")); // 43, 42, 45 -> exit
|
|
234 a.reset(cache.Access("47")); // 45 is removed; 47, 43, 42 -> exit
|
|
235 a.reset(cache.Access("44")); // 42 is removed; 44, 47, 43 -> exit
|
|
236 a.reset(cache.Access("42")); // 43 is removed; 42, 44, 47 -> exit
|
|
237 // Closing the cache: 47, 44, 42 are successively removed
|
|
238 }
|
|
239
|
|
240 ASSERT_EQ("45 42 43 47 44 42 ", provider.log_);
|
|
241 }
|