Merge lp:~mandel/ubuntu-system-settings/fix-network into lp:ubuntu-system-settings

Proposed by Manuel de la Peña on 2014-12-16
Status: Rejected
Rejected by: Manuel de la Peña on 2015-08-20
Proposed branch: lp:~mandel/ubuntu-system-settings/fix-network
Merge into: lp:ubuntu-system-settings
Diff against target: 931 lines (+594/-88)
14 files modified
CMakeLists.txt (+5/-1)
cmake/FindGtest.cmake (+53/-0)
debian/changelog (+6/-0)
debian/control (+4/-2)
plugins/system-update/download_tracker.cpp (+1/-1)
plugins/system-update/network.cpp (+138/-59)
plugins/system-update/network.h (+14/-7)
plugins/system-update/plugin/CMakeLists.txt (+22/-3)
plugins/system-update/update.h (+6/-6)
plugins/system-update/update_manager.h (+1/-1)
tests/plugins/system-update/CMakeLists.txt (+28/-7)
tests/plugins/system-update/mock_update.h (+43/-0)
tests/plugins/system-update/tst_network.cpp (+272/-0)
tests/plugins/system-update/tst_update.cpp (+1/-1)
To merge this branch: bzr merge lp:~mandel/ubuntu-system-settings/fix-network
Reviewer Review Type Date Requested Status
Sebastien Bacher (community) Needs Fixing on 2015-03-25
Ken VanDine 2014-12-16 Needs Fixing on 2015-01-26
PS Jenkins bot continuous-integration Needs Fixing on 2014-12-16
Review via email: mp+244845@code.launchpad.net

Commit Message

Improve the network code in the system-updates plugin.

Description of the Change

The current code in the system-updates that deals with the network is not "ideal". This branch simplifies the network interaction and add new tests that make sure that we react correctly to possible json errors.

To post a comment you must log in.
1231. By Manuel de la Peña on 2014-12-16

Merged with trunk.

1232. By Manuel de la Peña on 2014-12-16

Added missing tests.

Ken VanDine (ken-vandine) wrote :

There's a merge conflict

review: Needs Fixing
Sebastien Bacher (seb128) wrote :

can you rebase on current trunk?

review: Needs Fixing
Sebastien Bacher (seb128) wrote :

Hello?

Manuel de la Peña (mandel) wrote :

Doing a smaller change to fix this step by step.

Unmerged revisions

1232. By Manuel de la Peña on 2014-12-16

Added missing tests.

1231. By Manuel de la Peña on 2014-12-16

Merged with trunk.

1230. By Manuel de la Peña on 2014-12-16

Improve the network code in the system-updates plugin.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-11-19 18:05:48 +0000
3+++ CMakeLists.txt 2014-12-16 16:28:50 +0000
4@@ -27,8 +27,12 @@
5 endif()
6
7
8-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -fno-permissive -pedantic -Wall -Wextra")
9+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -fno-permissive -pedantic -Wall -Wextra -fPIC -pthread")
10 find_package(Qt5Widgets REQUIRED)
11+
12+find_package(Gtest REQUIRED)
13+include_directories(${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR})
14+
15 # Workaround for https://bugreports.qt-project.org/browse/QTBUG-29987
16 set(QT_IMPORTS_DIR "${CMAKE_INSTALL_LIBDIR}/qt5/qml")
17 set(CMAKE_AUTOMOC ON)
18
19=== added file 'cmake/FindGtest.cmake'
20--- cmake/FindGtest.cmake 1970-01-01 00:00:00 +0000
21+++ cmake/FindGtest.cmake 2014-12-16 16:28:50 +0000
22@@ -0,0 +1,53 @@
23+include(ExternalProject)
24+include(FindPackageHandleStandardArgs)
25+
26+#gtest
27+set(GTEST_INSTALL_DIR /usr/src/gmock/gtest/include)
28+find_path(GTEST_INCLUDE_DIR gtest/gtest.h
29+ HINTS ${GTEST_INSTALL_DIR})
30+
31+#gmock
32+find_path(GMOCK_INSTALL_DIR gmock/CMakeLists.txt
33+ HINTS /usr/src)
34+if(${GMOCK_INSTALL_DIR} STREQUAL "GMOCK_INSTALL_DIR-NOTFOUND")
35+ message(FATAL_ERROR "google-mock package not found")
36+endif()
37+
38+set(GMOCK_INSTALL_DIR ${GMOCK_INSTALL_DIR}/gmock)
39+find_path(GMOCK_INCLUDE_DIR gmock/gmock.h)
40+
41+set(GMOCK_PREFIX gmock)
42+set(GMOCK_BINARY_DIR ${CMAKE_BINARY_DIR}/${GMOCK_PREFIX}/libs)
43+set(GTEST_BINARY_DIR ${GMOCK_BINARY_DIR}/gtest)
44+
45+set(GTEST_CMAKE_ARGS "")
46+if (${MIR_IS_CROSS_COMPILING})
47+ set(GTEST_CMAKE_ARGS
48+ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_MODULE_PATH}/LinuxCrossCompile.cmake)
49+endif()
50+
51+ExternalProject_Add(
52+ GMock
53+ #where to build in source tree
54+ PREFIX ${GMOCK_PREFIX}
55+ #where the source is external to the project
56+ SOURCE_DIR ${GMOCK_INSTALL_DIR}
57+ #forward the compilers to the subproject so cross-arch builds work
58+ CMAKE_ARGS ${GTEST_CMAKE_ARGS}
59+ BINARY_DIR ${GMOCK_BINARY_DIR}
60+
61+ #we don't need to install, so skip
62+ INSTALL_COMMAND ""
63+)
64+
65+set(GMOCK_LIBRARY ${GMOCK_BINARY_DIR}/libgmock.a)
66+set(GMOCK_MAIN_LIBRARY ${GMOCK_BINARY_DIR}/libgmock_main.a)
67+set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY})
68+set(GTEST_LIBRARY ${GTEST_BINARY_DIR}/libgtest.a)
69+set(GTEST_MAIN_LIBRARY ${GTEST_BINARY_DIR}/libgtest_main.a)
70+set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
71+set(GTEST_ALL_LIBRARIES ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES})
72+
73+find_package_handle_standard_args(GTest DEFAULT_MSG
74+ GMOCK_INCLUDE_DIR
75+ GTEST_INCLUDE_DIR)
76
77=== modified file 'debian/changelog'
78--- debian/changelog 2014-12-11 21:15:07 +0000
79+++ debian/changelog 2014-12-16 16:28:50 +0000
80@@ -7,6 +7,12 @@
81
82 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 11 Dec 2014 21:15:06 +0000
83
84+ubuntu-system-settings (0.3+15.04.20141210.1-0ubuntu2) UNRELEASED; urgency=medium
85+
86+ * Greatly improve the network class in the system-updates plugin.
87+
88+ -- Manuel de la Pena <manuel.delapena@canonical.com> Tue, 16 Dec 2014 12:39:46 +0100
89+
90 ubuntu-system-settings (0.3+15.04.20141210.1-0ubuntu1) vivid; urgency=low
91
92 [ jonas-drange ]
93
94=== modified file 'debian/control'
95--- debian/control 2014-12-11 21:14:43 +0000
96+++ debian/control 2014-12-16 16:28:50 +0000
97@@ -44,7 +44,8 @@
98 python3-gi,
99 python3-dbus,
100 python3-xdg,
101- gir1.2-glib-2.0
102+ gir1.2-glib-2.0,
103+ google-mock
104 Standards-Version: 3.9.6
105 Homepage: https://launchpad.net/ubuntu-system-settings
106 # If you aren't a member of ~system-settings-touch but need to upload packaging
107@@ -89,7 +90,8 @@
108 python3-dbus,
109 python3-xdg,
110 gir1.2-glib-2.0,
111- qml-module-ubuntu-connectivity
112+ qml-module-ubuntu-connectivity,
113+ google-mock
114 Recommends: ubuntu-system-settings-online-accounts,
115 ubuntu-touch-sounds,
116 Conflicts: ubuntu-system-settings-example
117
118=== modified file 'plugins/system-update/download_tracker.cpp'
119--- plugins/system-update/download_tracker.cpp 2014-12-11 09:46:36 +0000
120+++ plugins/system-update/download_tracker.cpp 2014-12-16 16:28:50 +0000
121@@ -23,8 +23,8 @@
122 #include <ubuntu/download_manager/download_struct.h>
123 #include <ubuntu/download_manager/error.h>
124
125+#include "network.h"
126 #include "download_tracker.h"
127-#include "network/network.h"
128
129 namespace {
130 const QString DOWNLOAD_COMMAND = "post-download-command";
131
132=== removed directory 'plugins/system-update/network'
133=== renamed file 'plugins/system-update/network/network.cpp' => 'plugins/system-update/network.cpp'
134--- plugins/system-update/network/network.cpp 2014-09-18 13:50:15 +0000
135+++ plugins/system-update/network.cpp 2014-12-16 16:28:50 +0000
136@@ -16,18 +16,29 @@
137 * Boston, MA 02110-1301, USA.
138 */
139
140-#include "network.h"
141+#include <QByteArray>
142+#include <QDebug>
143 #include <QJsonDocument>
144 #include <QJsonObject>
145 #include <QJsonArray>
146 #include <QJsonValue>
147-#include <QByteArray>
148+#include <QProcessEnvironment>
149 #include <QUrl>
150-#include <QProcessEnvironment>
151-
152-#define URL_APPS "https://myapps.developer.ubuntu.com/dev/api/click-metadata/"
153-
154-#define APPS_DATA "APPS_DATA"
155+
156+#include "network.h"
157+
158+namespace {
159+ const QString CLICK_TOKEN_URL_ENV = "CLICK_TOKEN_URL";
160+ const QString URL_APPS_ENV = "URL_APPS";
161+ const QString URL_APPS =
162+ "https://myapps.developer.ubuntu.com/dev/api/click-metadata/";
163+ const QString APPS_DATA = "APPS_DATA";
164+ const QString NAME_KEY = "name";
165+ const QString VERSION_KEY = "version";
166+ const QString ICON_KEY = "icon_url";
167+ const QString DOWNLOAD_URL_KEY = "download_url";
168+ const QString BINARY_SIZE_KEY = "binary_filesize";
169+}
170
171 namespace UpdatePlugin {
172
173@@ -35,8 +46,12 @@
174 QObject(parent),
175 m_nam(this)
176 {
177- QObject::connect(&m_nam, SIGNAL(finished(QNetworkReply*)),
178- this, SLOT(onReply(QNetworkReply*)));
179+}
180+
181+Network::Network(QHash<QString, Update*> apps, QObject *parent) :
182+ Network(parent)
183+{
184+ m_apps = apps;
185 }
186
187 void Network::checkForNewVersions(QHash<QString, Update*> &apps)
188@@ -45,33 +60,109 @@
189
190 QJsonObject serializer;
191 QJsonArray array;
192+
193 foreach(QString id, m_apps.keys()) {
194 array.append(QJsonValue(m_apps.value(id)->getPackageName()));
195 }
196+ serializer.insert(NAME_KEY, array);
197
198- serializer.insert("name", array);
199 QJsonDocument doc(serializer);
200
201- QByteArray content = doc.toJson();
202+ auto content = doc.toJson();
203+ auto urlApps = getUrlApps();
204
205- QString urlApps = getUrlApps();
206- QNetworkRequest request;
207+ QNetworkRequest request(urlApps);
208 request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
209- request.setUrl(QUrl(urlApps));
210- RequestObject* reqObject = new RequestObject(QString(APPS_DATA));
211+
212+ auto reqObject = new RequestObject(APPS_DATA);
213 request.setOriginatingObject(reqObject);
214- m_nam.post(request, content);
215+
216+ auto reply = m_nam.post(request, content);
217+ connect(reply, &QNetworkReply::finished,
218+ this, &Network::onRequestFinished);
219 }
220
221-QString Network::getUrlApps()
222+QUrl Network::getUrlApps()
223 {
224- QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
225- QString command = environment.value("URL_APPS", QString(URL_APPS));
226+ auto environment = QProcessEnvironment::systemEnvironment();
227+ auto command = environment.value(URL_APPS_ENV, URL_APPS);
228 return command;
229 }
230
231-void Network::onReply(QNetworkReply *reply)
232-{
233+bool Network::parseUpdateObject(const QJsonValue& value)
234+{
235+ if (!value.isObject()) {
236+ qWarning() << "The value to parse is not an array.";
237+ return false;
238+ }
239+
240+ auto object = value.toObject();
241+
242+ if (object.contains(NAME_KEY)) {
243+ auto name = object[NAME_KEY].toString();
244+
245+ if (name.isEmpty()) {
246+ qWarning() << "Json reply contains a '" << NAME_KEY
247+ << "' value that is not a string.";
248+ return false;
249+ }
250+
251+ if (m_apps.contains(name)) {
252+ auto version = object[VERSION_KEY].toString();
253+ auto icon_url = object[ICON_KEY].toString();
254+ auto url = object[DOWNLOAD_URL_KEY].toString();
255+ auto size = object[BINARY_SIZE_KEY].toInt();
256+
257+ if (!version.isEmpty()) {
258+ m_apps[name]->setRemoteVersion(version);
259+ if (m_apps[name]->updateRequired()) {
260+ m_apps[name]->setIconUrl(icon_url);
261+ m_apps[name]->setDownloadUrl(url);
262+ m_apps[name]->setBinaryFilesize(size);
263+ return true;
264+ }
265+ } else {
266+ qWarning() << "Json reply contains a '" << VERSION_KEY
267+ << "' value that is not a string.";
268+ return false;
269+ }
270+
271+ } else {
272+ // can we really get here? It means that an update was returned
273+ // for an application not present in the device
274+ qWarning() << "Reply has update for missing application";
275+ return false;
276+ }
277+ } else {
278+ qWarning() << "Json reply is missing the '" << NAME_KEY
279+ << "' key";
280+ return false;
281+ }
282+ return false;
283+}
284+
285+void Network::onHeadRequestFinished()
286+{
287+ auto reply = qobject_cast<QNetworkReply*>(sender());
288+ if (reply->hasRawHeader(X_CLICK_TOKEN)) {
289+ auto app = qobject_cast<Update*>(reply->request().originatingObject());
290+ if (app != nullptr) {
291+ QString header(reply->rawHeader(X_CLICK_TOKEN));
292+ Q_EMIT clickTokenObtained(app, header);
293+ } else {
294+ // is this even possible?
295+ qWarning() << "App missing from head request.";
296+ }
297+ } else {
298+ qWarning() << "HEader '" << X_CLICK_TOKEN << "' missing from head reply.";
299+ }
300+ reply->deleteLater();
301+}
302+
303+void Network::onRequestFinished()
304+{
305+ auto reply = qobject_cast<QNetworkReply*>(sender());
306+
307 if (reply->error() == QNetworkReply::NoError) {
308 QVariant statusAttr = reply->attribute(
309 QNetworkRequest::HttpStatusCodeAttribute);
310@@ -83,46 +174,30 @@
311 int httpStatus = statusAttr.toInt();
312
313 if (httpStatus == 200 || httpStatus == 201) {
314- if (reply->hasRawHeader(X_CLICK_TOKEN)) {
315- Update* app = qobject_cast<Update*>(
316- reply->request().originatingObject());
317- if (app != nullptr) {
318- QString header(reply->rawHeader(X_CLICK_TOKEN));
319- Q_EMIT clickTokenObtained(app, header);
320- }
321- reply->deleteLater();
322- return;
323- }
324-
325- QByteArray payload = reply->readAll();
326- QJsonDocument document = QJsonDocument::fromJson(payload);
327-
328- RequestObject* state = qobject_cast<RequestObject*>(reply->request().originatingObject());
329- if (state != nullptr && state->operation.contains(APPS_DATA) && document.isArray()) {
330- QJsonArray array = document.array();
331+ auto payload = reply->readAll();
332+ auto document = QJsonDocument::fromJson(payload);
333+
334+ auto state = qobject_cast<RequestObject*>(
335+ reply->request().originatingObject());
336+
337+ if (state != nullptr
338+ && state->operation.contains(APPS_DATA)
339+ && document.isArray()) {
340+
341+ auto array = document.array();
342 bool updates = false;
343- for (int i = 0; i < array.size(); i++) {
344- QJsonObject object = array.at(i).toObject();
345- QString name = object.value("name").toString();
346- QString version = object.value("version").toString();
347- QString icon_url = object.value("icon_url").toString();
348- QString url = object.value("download_url").toString();
349- int size = object.value("binary_filesize").toVariant().toInt();
350- if (m_apps.contains(name)) {
351- m_apps[name]->setRemoteVersion(version);
352- if (m_apps[name]->updateRequired()) {
353- m_apps[name]->setIconUrl(icon_url);
354- m_apps[name]->setDownloadUrl(url);
355- m_apps[name]->setBinaryFilesize(size);
356- updates = true;
357- }
358- }
359+
360+ foreach(const QJsonValue& value, array) {
361+ // parse the object and state if updates are needed
362+ updates = parseUpdateObject(value);
363 }
364+
365 if (updates) {
366 Q_EMIT updatesFound();
367 } else {
368 Q_EMIT updatesNotFound();
369 }
370+
371 } else {
372 Q_EMIT errorOccurred();
373 }
374@@ -142,14 +217,18 @@
375 void Network::getClickToken(Update *app, const QString &url,
376 const QString &authHeader)
377 {
378- QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
379- QString signUrl = environment.value("CLICK_TOKEN_URL", url);
380+ auto environment = QProcessEnvironment::systemEnvironment();
381+ auto signUrl = environment.value(CLICK_TOKEN_URL_ENV, url);
382+
383 QUrl query(signUrl);
384 query.setQuery(authHeader);
385- QNetworkRequest request;
386- request.setUrl(query);
387+
388+ QNetworkRequest request(query);
389 request.setOriginatingObject(app);
390- m_nam.head(request);
391+
392+ auto reply = m_nam.head(request);
393+ connect(reply, &QNetworkReply::finished,
394+ this, &Network::onHeadRequestFinished);
395 }
396
397 }
398
399=== renamed file 'plugins/system-update/network/network.h' => 'plugins/system-update/network.h'
400--- plugins/system-update/network/network.h 2014-10-07 17:50:03 +0000
401+++ plugins/system-update/network.h 2014-12-16 16:28:50 +0000
402@@ -19,11 +19,12 @@
403 #ifndef NETWORK_H
404 #define NETWORK_H
405
406+#include <QHash>
407 #include <QObject>
408-#include <QtNetwork/QNetworkAccessManager>
409-#include <QtNetwork/QNetworkReply>
410-#include <QHash>
411-#include "../update.h"
412+#include <QNetworkAccessManager>
413+#include <QNetworkReply>
414+
415+#include "update.h"
416
417 #define X_CLICK_TOKEN "X-Click-Token"
418
419@@ -51,6 +52,9 @@
420 void checkForNewVersions(QHash<QString, Update*> &apps);
421 void getClickToken(Update *app, const QString &url,
422 const QString &authHeader);
423+protected:
424+ // helper constructor that allows to set the hash map for testing purposes
425+ Network(QHash<QString, Update*> apps, QObject *parent=0);
426
427 Q_SIGNALS:
428 void updatesFound();
429@@ -60,14 +64,17 @@
430 void serverError();
431 void clickTokenObtained(Update *app, const QString &clickToken);
432
433+protected:
434+ QUrl getUrlApps();
435+ bool parseUpdateObject(const QJsonValue& value);
436+
437 private Q_SLOTS:
438- void onReply(QNetworkReply*);
439+ void onRequestFinished();
440+ void onHeadRequestFinished();
441
442 private:
443 QNetworkAccessManager m_nam;
444 QHash<QString, Update*> m_apps;
445-
446- QString getUrlApps();
447 };
448
449 }
450
451=== modified file 'plugins/system-update/plugin/CMakeLists.txt'
452--- plugins/system-update/plugin/CMakeLists.txt 2014-10-22 19:37:42 +0000
453+++ plugins/system-update/plugin/CMakeLists.txt 2014-12-16 16:28:50 +0000
454@@ -1,9 +1,28 @@
455 SET (CMAKE_AUTOMOC ON)
456 include_directories(${CMAKE_CURRENT_BINARY_DIR})
457
458-add_library(update-plugin SHARED update-plugin.h update-plugin.cpp ../network/network.cpp ../network/network.h
459-../update.cpp ../update.h ../update_manager.cpp ../update_manager.h ../system_update.cpp ../system_update.h
460-../download_tracker.h ../download_tracker.cpp)
461+set(SOURCES
462+ update-plugin.cpp
463+ ../network.cpp
464+ ../update.cpp
465+ ../update_manager.cpp
466+ ../system_update.cpp
467+ ../download_tracker.cpp
468+)
469+
470+set(HEADERS
471+ update-plugin.h
472+ ../network.h
473+ ../update.h
474+ ../update_manager.h
475+ ../system_update.h
476+ ../download_tracker.h
477+)
478+
479+add_library(update-plugin SHARED
480+ ${HEADERS}
481+ ${SOURCES}
482+)
483
484 qt5_use_modules(update-plugin Core Qml Quick Network DBus)
485 include_directories(/usr/include/apt-pkg/)
486
487=== modified file 'plugins/system-update/update.h'
488--- plugins/system-update/update.h 2014-09-12 19:12:36 +0000
489+++ plugins/system-update/update.h 2014-12-16 16:28:50 +0000
490@@ -78,7 +78,7 @@
491
492 public:
493 explicit Update(QObject *parent = 0);
494- virtual ~Update();
495+ ~Update();
496
497 bool systemUpdate() { return m_systemUpdate; }
498 QString getPackageName() { return m_packagename; }
499@@ -89,7 +89,7 @@
500 QString lastUpdateDate() { return m_lastUpdateDate; }
501 int binaryFilesize() { return m_binary_filesize; }
502 int downloadProgress() { return m_download_progress; }
503- bool updateRequired() { return m_update; }
504+ virtual bool updateRequired() { return m_update; }
505 bool updateState() { return m_update_state; }
506 bool updateReady() { return m_update_ready; }
507 bool selected() { return m_selected; }
508@@ -101,19 +101,19 @@
509 void setSystemUpdate(bool isSystem);
510 void initializeApplication(QString packagename, QString title,
511 QString version);
512- void setRemoteVersion(QString &version);
513+ virtual void setRemoteVersion(QString &version);
514 void setUpdateRequired(bool state);
515 void setUpdateState(bool state);
516 void setUpdateReady(bool ready);
517 void setSelected(bool value);
518- void setBinaryFilesize(int size);
519+ virtual void setBinaryFilesize(int size);
520 void setDownloadProgress(int progress);
521- void setIconUrl(QString icon);
522+ virtual void setIconUrl(QString icon);
523 void setError(QString error);
524 void setUpdateAvailable(bool available) { m_update = available; }
525 void setLastUpdateDate(const QString date);
526 void setClickUrl(const QString &url) { m_click_url = url; }
527- void setDownloadUrl(const QString &url);
528+ virtual void setDownloadUrl(const QString &url);
529 void setClickToken(const QString &token) { m_clickToken = token; Q_EMIT clickTokenChanged(); }
530
531 private:
532
533=== modified file 'plugins/system-update/update_manager.h'
534--- plugins/system-update/update_manager.h 2014-10-24 20:06:13 +0000
535+++ plugins/system-update/update_manager.h 2014-12-16 16:28:50 +0000
536@@ -40,7 +40,7 @@
537 #else
538 #include <ssoservice.h>
539 #include <QProcess>
540-#include "network/network.h"
541+#include "network.h"
542 #include "system_update.h"
543 #endif
544
545
546=== modified file 'tests/plugins/system-update/CMakeLists.txt'
547--- tests/plugins/system-update/CMakeLists.txt 2014-10-23 13:27:05 +0000
548+++ tests/plugins/system-update/CMakeLists.txt 2014-12-16 16:28:50 +0000
549@@ -25,16 +25,37 @@
550 ../../../plugins/system-update/update.cpp
551 )
552
553+add_executable(tst-network
554+ mock_update.h
555+ tst_network.cpp
556+ ../../../plugins/system-update/update.cpp
557+ ../../../plugins/system-update/network.cpp
558+)
559+
560 # set the path to the library folder
561 include_directories(/usr/include/apt-pkg/)
562
563-target_link_libraries(tst-update-manager apt-pkg update-plugin ${UBUNTUONEAUTH_LDFLAGS})
564-qt5_use_modules(tst-update-manager Qml Quick Core DBus Xml Network Test)
565-add_test(NAME tst-update-manager COMMAND ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/tst-update-manager)
566-
567-qt5_use_modules(tst-update Qml Quick Core DBus Xml Network Test)
568-target_link_libraries(tst-update apt-pkg update-plugin)
569-add_test(NAME tst-update COMMAND ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/tst-update)
570+set(UNIT_TESTS
571+ tst-update-manager
572+ tst-update
573+ tst-network
574+)
575+
576+foreach(test ${UNIT_TESTS})
577+ qt5_use_modules(${test} Qml Quick Core DBus Xml Network Test)
578+
579+ target_link_libraries(${test}
580+ apt-pkg
581+ update-plugin
582+ ${UBUNTUONEAUTH_LDFLAGS}
583+ ${UBUNTU_DOWNLOAD_MANAGER_CLIENT_LDFLAGS}
584+ ${UBUNTU_DOWNLOAD_MANAGER_COMMON_LDFLAGS}
585+ ${GMOCK_LIBRARY}
586+ ${GTEST_BOTH_LIBRARIES}
587+ )
588+ add_test(NAME ${test}
589+ COMMAND ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/${test})
590+endforeach(test)
591
592 add_custom_command(
593 TARGET tst-update-manager
594
595=== added file 'tests/plugins/system-update/mock_update.h'
596--- tests/plugins/system-update/mock_update.h 1970-01-01 00:00:00 +0000
597+++ tests/plugins/system-update/mock_update.h 2014-12-16 16:28:50 +0000
598@@ -0,0 +1,43 @@
599+/*
600+ * Copyright (C) 2014 Canonical Ltd
601+ *
602+ * This program is free software: you can redistribute it and/or modify
603+ * it under the terms of the GNU General Public License version 3 as
604+ * published by the Free Software Foundation.
605+ *
606+ * This program is distributed in the hope that it will be useful,
607+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
608+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
609+ * GNU General Public License for more details.
610+ *
611+ * You should have received a copy of the GNU General Public License
612+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
613+ *
614+ * Authors:
615+ * Manuel de la Pena <manuel.delapena@canonical.com>
616+ *
617+ */
618+
619+#ifndef MOCKED_UPDATE_H
620+#define MOCKED_UPDATE_H
621+
622+#include <gmock/gmock.h>
623+
624+#include "update.h"
625+
626+using namespace UpdatePlugin;
627+
628+class MockedUpdate : public Update {
629+ Q_OBJECT
630+public:
631+ explicit MockedUpdate(QObject *parent = 0)
632+ : Update(parent) {}
633+
634+ MOCK_METHOD1(setRemoteVersion, void(QString&));
635+ MOCK_METHOD1(setBinaryFilesize, void(int));
636+ MOCK_METHOD0(updateRequired, bool());
637+ MOCK_METHOD1(setIconUrl, void(QString));
638+ MOCK_METHOD1(setDownloadUrl, void(const QString&));
639+};
640+
641+#endif
642
643=== added file 'tests/plugins/system-update/tst_network.cpp'
644--- tests/plugins/system-update/tst_network.cpp 1970-01-01 00:00:00 +0000
645+++ tests/plugins/system-update/tst_network.cpp 2014-12-16 16:28:50 +0000
646@@ -0,0 +1,272 @@
647+/*
648+ * This file is part of system-settings
649+ *
650+ * Copyright (C) 2014 Canonical Ltd.
651+ *
652+ * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
653+ *
654+ * This program is free software: you can redistribute it and/or modify it
655+ * under the terms of the GNU General Public License version 3, as published
656+ * by the Free Software Foundation.
657+ *
658+ * This program is distributed in the hope that it will be useful, but
659+ * WITHOUT ANY WARRANTY; without even the implied warranties of
660+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
661+ * PURPOSE. See the GNU General Public License for more details.
662+ *
663+ * You should have received a copy of the GNU General Public License along
664+ * with this program. If not, see <http://www.gnu.org/licenses/>.
665+ */
666+
667+#include <QJsonArray>
668+#include <QJsonObject>
669+#include <QJsonValue>
670+#include <QObject>
671+#include <QTest>
672+#include <QScopedPointer>
673+#include <QString>
674+
675+#include <gmock/gmock.h>
676+
677+#include "mock_update.h"
678+#include "network.h"
679+
680+using ::testing::_;
681+using ::testing::Mock;
682+using ::testing::AnyNumber;
683+using ::testing::Return;
684+
685+using namespace UpdatePlugin;
686+
687+namespace {
688+ const QString NAME_KEY = "name";
689+ const QString VERSION_KEY = "version";
690+ const QString ICON_KEY = "icon_url";
691+ const QString DOWNLOAD_URL_KEY = "download_url";
692+ const QString BINARY_SIZE_KEY = "binary_filesize";
693+}
694+
695+// helper class that exposes protected members
696+class PublicNetwork : public Network
697+{
698+public:
699+ PublicNetwork(QHash<QString, Update*> apps, QObject *parent=0) :
700+ Network(apps, parent) {}
701+
702+ using Network::getUrlApps;
703+ using Network::parseUpdateObject;
704+};
705+
706+class NetworkTest: public QObject
707+{
708+ Q_OBJECT
709+
710+public:
711+ NetworkTest() : QObject() {};
712+
713+private Q_SLOTS:
714+ void testParseUpdateNotObject();
715+ void testParseUpdateMissingName();
716+ void testParseUpdateMissingVersion();
717+ void testParseUpdateRequired_data();
718+ void testParseUpdateRequired();
719+ void testParseUpdateNotRequired();
720+ void testParseUpdateMissingInHash();
721+};
722+
723+void NetworkTest::testParseUpdateNotObject()
724+{
725+ QJsonValue value(true);
726+
727+ QHash<QString, Update*> updates;
728+ QScopedPointer<PublicNetwork> network(new PublicNetwork(updates));
729+
730+ QVERIFY(!network->parseUpdateObject(value));
731+}
732+
733+void NetworkTest::testParseUpdateMissingName()
734+{
735+ QJsonObject obj;
736+ QJsonValue value(obj);
737+
738+ QHash<QString, Update*> updates;
739+ QScopedPointer<PublicNetwork> network(new PublicNetwork(updates));
740+
741+ QVERIFY(!network->parseUpdateObject(value));
742+}
743+
744+void NetworkTest::testParseUpdateMissingVersion()
745+{
746+ QJsonObject obj;
747+ obj["name"] = "test";
748+ QJsonValue value(obj);
749+
750+ QHash<QString, Update*> updates;
751+ QScopedPointer<PublicNetwork> network(new PublicNetwork(updates));
752+
753+ QVERIFY(!network->parseUpdateObject(value));
754+}
755+
756+void NetworkTest::testParseUpdateRequired_data()
757+{
758+ QTest::addColumn<QHash<QString, Update*>>("updates");
759+ QTest::addColumn<QJsonValue>("json");
760+
761+ QString firstName = "first-app";
762+ QString secondName = "second-app";
763+
764+ QJsonObject firstJson;
765+ firstJson[NAME_KEY] = firstName;
766+ firstJson[VERSION_KEY] = QJsonValue(QString("2.2"));
767+ firstJson[ICON_KEY] = QJsonValue(QString("www.test.com/first-app-icon"));
768+ firstJson[DOWNLOAD_URL_KEY] = QJsonValue(QString("www.test.com/first-app"));
769+ firstJson[BINARY_SIZE_KEY] = QJsonValue(23.3);
770+
771+ QJsonObject secondJson;
772+ secondJson[NAME_KEY] = secondName;
773+ secondJson[VERSION_KEY] = QJsonValue(QString("1.2"));
774+ secondJson[ICON_KEY] = QJsonValue(QString("www.test.com/second-app-icon"));
775+ secondJson[DOWNLOAD_URL_KEY] = QJsonValue(QString("www.test.com/second-app"));
776+ secondJson[BINARY_SIZE_KEY] = QJsonValue(3.3);
777+
778+ QHash<QString, Update*> singleUpdate;
779+ singleUpdate[firstName] = new MockedUpdate();
780+
781+ QHash<QString, Update*> severalUpdates;
782+ severalUpdates[firstName] = new MockedUpdate();
783+ severalUpdates[secondName] = new MockedUpdate();
784+
785+ QTest::newRow("single-obj") << singleUpdate << QJsonValue(firstJson);
786+ QTest::newRow("several-obj") << severalUpdates << QJsonValue(secondJson);
787+}
788+
789+void NetworkTest::testParseUpdateRequired()
790+{
791+ // the typedef is needed so that QFETCH does work
792+ typedef QHash<QString, Update*> UpdatesHash;
793+ QFETCH(UpdatesHash, updates);
794+ QFETCH(QJsonValue, json);
795+
796+ auto jsonObj = json.toObject();
797+ auto jsonName = jsonObj[NAME_KEY].toString();
798+ if (jsonName.isEmpty()) {
799+ QFAIL("Wrong test data.");
800+ }
801+
802+ auto version = jsonObj[VERSION_KEY].toString();
803+ auto icon_url = jsonObj[ICON_KEY].toString();
804+ auto url = jsonObj[DOWNLOAD_URL_KEY].toString();
805+ auto size = jsonObj[BINARY_SIZE_KEY].toInt();
806+
807+ // set the expectations of the updates, only the update with the same name
808+ // as the json will have the methods called once, the rest should not be
809+ // called at all
810+ foreach(const QString& name, updates.keys()) {
811+ auto update = qobject_cast<MockedUpdate*>(updates[name]);
812+ if (name == jsonName) {
813+ // set expectations
814+ EXPECT_CALL(*update, setRemoteVersion(version))
815+ .Times(1);
816+
817+ EXPECT_CALL(*update, updateRequired())
818+ .Times(1)
819+ .WillOnce(Return(true));
820+
821+ EXPECT_CALL(*update, setIconUrl(icon_url))
822+ .Times(1);
823+
824+ EXPECT_CALL(*update, setDownloadUrl(url))
825+ .Times(1);
826+
827+ EXPECT_CALL(*update, setBinaryFilesize(size))
828+ .Times(1);
829+ } else {
830+ // methods most not be called
831+ EXPECT_CALL(*update, setRemoteVersion(version))
832+ .Times(0);
833+
834+ EXPECT_CALL(*update, updateRequired())
835+ .Times(0);
836+
837+ EXPECT_CALL(*update, setIconUrl(icon_url))
838+ .Times(0);
839+
840+ EXPECT_CALL(*update, setDownloadUrl(url))
841+ .Times(0);
842+
843+ EXPECT_CALL(*update, setBinaryFilesize(size))
844+ .Times(0);
845+ }
846+ }
847+
848+ QScopedPointer<PublicNetwork> network(new PublicNetwork(updates));
849+ QVERIFY(network->parseUpdateObject(json));
850+
851+ // verify each of the mocked objects
852+ foreach(Update* update, updates) {
853+ auto mock = qobject_cast<MockedUpdate*>(update);
854+ QVERIFY(Mock::VerifyAndClearExpectations(mock));
855+ }
856+ // we must delete all the mocks
857+ qDeleteAll(updates);
858+}
859+
860+void NetworkTest::testParseUpdateNotRequired()
861+{
862+ // create updates with a mock obj that does not need an update
863+ QString appName = "first-app";
864+
865+ QJsonObject json;
866+ json[NAME_KEY] = appName;
867+ json[VERSION_KEY] = QJsonValue(QString("2.2"));
868+ json[ICON_KEY] = QJsonValue(QString("www.test.com/first-app-icon"));
869+ json[DOWNLOAD_URL_KEY] = QJsonValue(QString("www.test.com/first-app"));
870+ json[BINARY_SIZE_KEY] = QJsonValue(23.3);
871+
872+ auto update = new MockedUpdate();
873+ QHash<QString, Update*> updates;
874+ updates[appName] = update;
875+
876+ // set expectations
877+ EXPECT_CALL(*update, setRemoteVersion(_))
878+ .Times(1);
879+
880+ EXPECT_CALL(*update, updateRequired())
881+ .Times(1)
882+ .WillOnce(Return(false));
883+
884+ EXPECT_CALL(*update, setIconUrl(_))
885+ .Times(0);
886+
887+ EXPECT_CALL(*update, setDownloadUrl(_))
888+ .Times(0);
889+
890+ EXPECT_CALL(*update, setBinaryFilesize(_))
891+ .Times(0);
892+
893+ QScopedPointer<PublicNetwork> network(new PublicNetwork(updates));
894+ QVERIFY(!network->parseUpdateObject(json));
895+ QVERIFY(Mock::VerifyAndClearExpectations(update));
896+ qDeleteAll(updates);
897+}
898+
899+void NetworkTest::testParseUpdateMissingInHash()
900+{
901+ // send an update for a missing app
902+ QString appName = "first-app";
903+
904+ QJsonObject json;
905+ json[NAME_KEY] = appName;
906+ json[VERSION_KEY] = QJsonValue(QString("2.2"));
907+ json[ICON_KEY] = QJsonValue(QString("www.test.com/first-app-icon"));
908+ json[DOWNLOAD_URL_KEY] = QJsonValue(QString("www.test.com/first-app"));
909+ json[BINARY_SIZE_KEY] = QJsonValue(23.3);
910+
911+ QHash<QString, Update*> updates;
912+
913+ QScopedPointer<PublicNetwork> network(new PublicNetwork(updates));
914+ QVERIFY(!network->parseUpdateObject(json));
915+}
916+
917+QTEST_MAIN(NetworkTest)
918+#include "tst_network.moc"
919
920=== modified file 'tests/plugins/system-update/tst_update.cpp'
921--- tests/plugins/system-update/tst_update.cpp 2014-08-20 12:50:09 +0000
922+++ tests/plugins/system-update/tst_update.cpp 2014-12-16 16:28:50 +0000
923@@ -35,7 +35,7 @@
924 Q_OBJECT
925
926 public:
927- UpdateTest() {};
928+ UpdateTest() : QObject() {};
929
930 private Q_SLOTS:
931 void testCompareVersion();

Subscribers

People subscribed via source and target branches