swift
testvaluecache.cpp
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
7 
8 #include <chrono>
9 #include <future>
10 #include <ratio>
11 
12 #include <QCoreApplication>
13 #include <QDateTime>
14 #include <QDir>
15 #include <QFileInfo>
16 #include <QFlags>
17 #include <QJsonObject>
18 #include <QJsonValue>
19 #include <QList>
20 #include <QMetaObject>
21 #include <QRegularExpression>
22 #include <QString>
23 #include <QTest>
24 #include <QThread>
25 #include <QTimer>
26 #include <QtDebug>
27 
28 #include "test.h"
29 
32 #include "misc/dictionary.h"
33 #include "misc/identifier.h"
34 #include "misc/registermetadata.h"
37 #include "misc/statusmessage.h"
38 #include "misc/valuecache.h"
39 #include "misc/variant.h"
40 #include "misc/variantmap.h"
41 #include "misc/worker.h"
42 
43 namespace MiscTest
44 {
45  using namespace swift::misc;
46  using namespace swift::misc::aviation;
47  using namespace swift::misc::simulation;
48 
50  class CTestValueCache : public QObject
51  {
52  Q_OBJECT
53 
54  private slots:
56  void initTestCase();
57 
59  void insertAndGet();
60 
62  void localOnly();
63 
65  void localOnlyWithThreads();
66 
68  void distributed();
69 
71  void batched();
72 
74  void json();
75 
77  void saveAndLoad();
78  };
79 
81  class CValueCacheUser : public QObject
82  {
83  Q_OBJECT
84 
85  public:
88 
90  void ps_valueChanged();
91 
93  bool slotFired();
94 
95  std::promise<void> m_slotFired;
98  };
99 
100  void CTestValueCache::initTestCase() { swift::misc::registerMetadata(); }
101 
102  void CTestValueCache::insertAndGet()
103  {
104  CVariantMap testData { { "value1", CVariant::from(1) },
105  { "value2", CVariant::from(2) },
106  { "value3", CVariant::from(3) } };
107  CVariantMap testData2 { { "value2", CVariant::from(42) }, { "value4", CVariant::from(4) } };
108  CVariantMap testDataCombined { { "value1", CVariant::from(1) },
109  { "value2", CVariant::from(42) },
110  { "value3", CVariant::from(3) },
111  { "value4", CVariant::from(4) } };
112 
113  CValueCache cache(1);
114  QVERIFY(cache.getAllValues() == CVariantMap());
115  cache.insertValues({ testData, QDateTime::currentMSecsSinceEpoch() });
116  QVERIFY(cache.getAllValues() == testData);
117  cache.insertValues({ testData2, QDateTime::currentMSecsSinceEpoch() });
118  QVERIFY(cache.getAllValues() == testDataCombined);
119  }
120 
122  void waitForQueueOf(QObject *object)
123  {
124  if (object->thread() != QThread::currentThread())
125  {
126  std::promise<void> promise;
127  QTimer::singleShot(0, object, [&] { promise.set_value(); });
128  promise.get_future().wait();
129  }
130  }
131 
132  template <typename F>
133  void singleShotAndWait(QObject *object, F task)
134  {
135  if (object->thread() == QThread::currentThread()) { task(); }
136  else
137  {
138  QTimer::singleShot(0, object, task);
139  waitForQueueOf(object);
140  }
141  }
142 
143  void testCommon(CValueCacheUser &user1, CValueCacheUser &user2)
144  {
145  user1.m_value1.set(42);
146  QVERIFY(user2.slotFired());
147  QVERIFY(!user1.slotFired());
148  singleShotAndWait(&user2, [&] { QVERIFY(user2.m_value1.get() == 42); });
149  QVERIFY(user1.m_value1.get() == 42);
150 
151  user1.m_value2.set(42);
152  user2.slotFired();
153  auto status = user1.m_value2.set(-1337);
154  QVERIFY(status.isFailure());
155  QVERIFY(!user1.slotFired());
156  QVERIFY(!user2.slotFired());
157  singleShotAndWait(&user2, [&] { QVERIFY(user2.m_value2.get() == 42); });
158  QVERIFY(user1.m_value2.get() == 42);
159  }
161 
162  void CTestValueCache::localOnly()
163  {
164  CValueCache cache(1);
165  for (int i = 0; i < 2; ++i) { QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Empty cache value")); }
166  CValueCacheUser user1(&cache);
167  CValueCacheUser user2(&cache);
168  testCommon(user1, user2);
169  }
170 
171  void CTestValueCache::localOnlyWithThreads()
172  {
173  CValueCache cache(1);
174  for (int i = 0; i < 2; ++i) { QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Empty cache value")); }
175  CValueCacheUser user1(&cache);
176  CValueCacheUser user2(&cache);
177  CRegularThread thread;
178  user2.moveToThread(&thread);
179  thread.start();
180  testCommon(user1, user2);
181  }
182 
183  void CTestValueCache::distributed()
184  {
185  CIdentifier thisProcess;
186  CIdentifier otherProcess;
187  auto json = otherProcess.toJson();
188  json.insert("processId", otherProcess.getProcessId() + 1);
189  otherProcess.convertFromJson(json);
190 
191  CValueCache thisCache(1);
192  CValueCache otherCache(1);
193  connect(&thisCache, &CValueCache::valuesChangedByLocal, &thisCache, [&](const CValueCachePacket &values) {
194  QMetaObject::invokeMethod(&thisCache,
195  [=, &thisCache] { thisCache.changeValuesFromRemote(values, thisProcess); });
196  QMetaObject::invokeMethod(&otherCache,
197  [=, &otherCache] { otherCache.changeValuesFromRemote(values, otherProcess); });
198  });
199  connect(&otherCache, &CValueCache::valuesChangedByLocal, &thisCache, [&](const CValueCachePacket &values) {
200  QMetaObject::invokeMethod(&thisCache,
201  [=, &thisCache] { thisCache.changeValuesFromRemote(values, otherProcess); });
202  QMetaObject::invokeMethod(&otherCache,
203  [=, &otherCache] { otherCache.changeValuesFromRemote(values, thisProcess); });
204  });
205 
206  for (int i = 0; i < 4; ++i) { QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Empty cache value")); }
207  CValueCacheUser thisUser(&thisCache);
208  CValueCacheUser otherUser(&otherCache);
209 
210  CRegularThread thread;
211  otherCache.moveToThread(&thread);
212  otherUser.moveToThread(&thread);
213  thread.start();
214 
215  singleShotAndWait(&otherUser, [&] { otherUser.m_value1.set(99); });
216  thisUser.m_value1.set(100);
217  QCoreApplication::processEvents();
218  waitForQueueOf(&otherUser);
219  QVERIFY(thisUser.slotFired() != otherUser.slotFired());
220  auto thisValue = thisUser.m_value1.get();
221  singleShotAndWait(&otherUser, [&] { QVERIFY(thisValue == otherUser.m_value1.get()); });
222  }
223 
224  void CTestValueCache::batched()
225  {
226  CValueCache cache(1);
227  for (int i = 0; i < 2; ++i) { QTest::ignoreMessage(QtDebugMsg, QRegularExpression("Empty cache value")); }
228  CValueCacheUser user1(&cache);
229  CValueCacheUser user2(&cache);
230 
231  {
232  auto batch = cache.batchChanges(&user1);
233  user1.m_value1.set(42);
234  user1.m_value2.set(42);
235  }
236  QVERIFY(!user1.slotFired());
237  QVERIFY(user2.slotFired());
238  singleShotAndWait(&user2, [&] {
239  QVERIFY(user2.m_value1.get() == 42);
240  QVERIFY(user2.m_value2.get() == 42);
241  });
242  }
243 
244  void CTestValueCache::json()
245  {
246  QJsonObject testJson { { "value1", CVariant::from(1).toJson() },
247  { "value2", CVariant::from(2).toJson() },
248  { "value3", CVariant::from(3).toJson() } };
249  CVariantMap testData { { "value1", CVariant::from(1) },
250  { "value2", CVariant::from(2) },
251  { "value3", CVariant::from(3) } };
252 
253  CValueCache cache(1);
254  cache.loadFromJson(testJson);
255  QVERIFY(cache.getAllValues() == testData);
256  QVERIFY(cache.saveToJson() == testJson);
257  }
258 
259  void CTestValueCache::saveAndLoad()
260  {
261  CSimulatedAircraftList aircraft({ CSimulatedAircraft("BAW001", {}, {}) });
262  CAtcStationList atcStations({ CAtcStation("EGLL_TWR") });
263  const CVariantMap testData { { "namespace1/value1", CVariant::from(1) },
264  { "namespace1/value2", CVariant::from(2) },
265  { "namespace1/value3", CVariant::from(3) },
266  { "namespace2/aircraft", CVariant::from(aircraft) },
267  { "namespace2/atcstations", CVariant::from(atcStations) } };
268  CValueCache cache(1);
269  cache.insertValues({ testData, QDateTime::currentMSecsSinceEpoch() });
270 
271  QDir dir(QDir::currentPath() + "/testcache");
272  if (dir.exists()) { dir.removeRecursively(); }
273 
274  auto status = cache.saveToFiles(dir.absolutePath());
275  QVERIFY(status.isSuccess());
276 
277  auto files = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name);
278  QCOMPARE(files.size(), 2);
279  QCOMPARE(files[0].fileName(), QString("namespace1.json"));
280  QCOMPARE(files[1].fileName(), QString("namespace2.json"));
281 
282  CValueCache cache2(1);
283  status = cache2.loadFromFiles(dir.absolutePath());
284  QVERIFY(status.isSuccess());
285  const CVariantMap test2Values = cache2.getAllValues();
286  QCOMPARE(test2Values, testData);
287  }
288 
290  bool validator(int value, QString &) { return value >= 0 && value <= 100; }
291 
293  : m_value1(cache, "value1", "", validator, 0, this), m_value2(cache, "value2", "", validator, 0, this)
294  {
297  }
298 
300 
302  {
303  auto status = m_slotFired.get_future().wait_for(std::chrono::milliseconds(250));
304  m_slotFired = std::promise<void>();
305  switch (status)
306  {
307  case std::future_status::ready: return true;
308  case std::future_status::timeout: return false;
309  case std::future_status::deferred:
310  default: QTEST_ASSERT(false);
311  }
312  return false;
313  }
314 } // namespace MiscTest
315 
318 
319 #include "testvaluecache.moc"
320 
Unit tests for value cache system.
Simple class which uses CCached, for testing.
std::promise< void > m_slotFired
Flag marking whether the slot was called.
bool slotFired()
Detect whether the slot was called, for verification.
swift::misc::CCached< int > m_value1
First cached value.
swift::misc::CCached< int > m_value2
Second cached value.
void ps_valueChanged()
Slot to be called when a cached value changes.
CValueCacheUser(swift::misc::CValueCache *cache)
Constructor.
void setNotifySlot(F slot)
Set a callback to be called when the value is changed by another source.
Definition: valuecache.h:384
Value object encapsulating information identifying a component of a modular distributed swift process...
Definition: identifier.h:29
qint64 getProcessId() const
Get process id.
Definition: identifier.h:115
Just a subclass of QThread whose destructor waits for the thread to finish.
Definition: worker.h:38
Manages a map of { QString, CVariant } pairs, which can be distributed among multiple processes.
Definition: valuecache.h:154
void valuesChangedByLocal(const swift::misc::CValueCachePacket &values)
Emitted when values in the cache are changed by an object in this very process. The interprocess comm...
Value class used for signalling changed values in the cache.
Definition: valuecache.h:67
static CVariant from(T &&value)
Synonym for fromValue().
Definition: variant.h:147
QJsonObject toJson() const
Cast to JSON object.
Map of { QString, CVariant } pairs.
Definition: variantmap.h:35
Value object encapsulating information about an ATC station.
Definition: atcstation.h:38
Value object for a list of ATC stations.
void convertFromJson(const QJsonObject &json)
Assign from JSON object.
Definition: mixinjson.h:153
QJsonObject toJson() const
Cast to JSON object.
Definition: mixinjson.h:132
Comprehensive information of an aircraft.
Value object encapsulating a list of aircraft.
Free functions in swift::misc.
void registerMetadata()
Register all relevant metadata in Misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
SWIFTTEST_MAIN(MiscTest::CTestValueCache)
main
bool validator(int value, QString &)
Is value between 0 - 100?