Merge lp:~charlesk/keeper/add-keeper-dbusmock-template into lp:keeper

Proposed by Charles Kerr
Status: Merged
Merged at revision: 43
Proposed branch: lp:~charlesk/keeper/add-keeper-dbusmock-template
Merge into: lp:keeper
Prerequisite: lp:~charlesk/keeper/add-tar-creator
Diff against target: 1086 lines (+944/-9)
13 files modified
debian/control (+4/-0)
src/qdbus-stubs/CMakeLists.txt (+11/-1)
src/qdbus-stubs/com.canonical.keeper.User.xml (+1/-1)
tests/CMakeLists.txt (+29/-1)
tests/com_canonical_keeper.py (+435/-0)
tests/dbusmock/CMakeLists.txt (+62/-0)
tests/dbusmock/fake-backup-helper.cpp (+63/-0)
tests/dbusmock/fake-backup-helper.h (+38/-0)
tests/dbusmock/keeper-template-test.cpp (+285/-0)
tests/surface/CMakeLists.txt (+1/-0)
tests/unit/helper/CMakeLists.txt (+0/-2)
tests/utils/CMakeLists.txt (+11/-4)
tests/utils/main.cpp (+4/-0)
To merge this branch: bzr merge lp:~charlesk/keeper/add-keeper-dbusmock-template
Reviewer Review Type Date Requested Status
Xavi Garcia (community) Approve
Marcus Tomlinson Pending
Unity API Team Pending
Review via email: mp+299498@code.launchpad.net

Commit message

add a keeper dbusmock for testing/prototyping

Description of the change

Add a keeper dbusmock for testing/prototyping.

Supported bits:
com.canonical.keeper.User.GetBackupChoices()
com.canonical.keeper.User.GetRestoreChoices()
com.canonical.keeper.User.StartBackup()
com.canonical.keeper.Helper.StartBackup()
com.canonical.keeper.Mock.AddBackupChoice()
com.canonical.keeper.Mock.AddRestoreChoice()
com.canonical.keeper.Mock.GetBackupData()

Example use in tests/dbusmock/keeper-keeper-template-test.cpp

Coming next:
com.canonical.keeper.User.Cancel()
com.canonical.keeper.User.StartRestore()
com.canonical.keeper.Helper.StartRestore()

To post a comment you must log in.
109. By Charles Kerr

in keeper-template-test, add test to check return values of com.canonical.keeper.User.RestoreChoices()

110. By Charles Kerr

in keeper-template-test, add test to check User.StartBackup() throws EINVAL if one of the uuids isn't a valid backup choice

111. By Charles Kerr

in keeper-template-test, add test to check User.StartRestore() throws EINVAL if one of the uuids isn't a valid restore choice

112. By Charles Kerr

in keeper-template-test, add test to check User.State() returns an empty set when we have no tasks queued

113. By Charles Kerr

in keeper-template-test, test User.StartBackup(), Helper.StartBackup(), and User.State() by adding a task that performs a backup

114. By Charles Kerr

in keeper mock's helper_backup_worker(), better error handling when recv() returns 0

115. By Charles Kerr

sync with lp:~charlesk/keeper/add-tar-creator

116. By Charles Kerr

sync with lp:~charlesk/keeper/add-tar-creator

117. By Charles Kerr

fix threading issues in the keeper dbusmock template

118. By Charles Kerr

tweak the error messages a little

119. By Charles Kerr

make the template's python3-gi dependencies explicit in debian/control

120. By Charles Kerr

more keeper dbusmock tepmlate tweaking

121. By Charles Kerr

make the env messages terser in the folder-helper fake

122. By Charles Kerr

rename folder-helper.cpp as fake-folder-helper.cpp

123. By Charles Kerr

sync with trunk

124. By Charles Kerr

add flake8 to debian build dep

125. By Charles Kerr

add testing methods to the mock service to let devs add choices and to see the results of a backup run

126. By Charles Kerr

add tests to run the dbusmock template through a full backup session

127. By Charles Kerr

sync with trunk

Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Thanks Charles.
I'm not very familiar with this kind of code defining mocks in python but I haven't seen anything weird. Just a couple of questions to understand the code better.

review: Needs Information
128. By Charles Kerr

when the last task finishes, clear the remaining_tasks list

Revision history for this message
Charles Kerr (charlesk) wrote :

Answers inline

129. By Charles Kerr

sync with trunk

130. By Charles Kerr

fix FTBFS by making implicit internal library dependency chain explicit

131. By Charles Kerr

fix whitespace test

132. By Charles Kerr

sync keeper-template-test with r128 changes

133. By Charles Kerr

fix typo in parsing input arguments to mock_add_backup_choice() and mock_add_restore_choice()

134. By Charles Kerr

when launching a backup helper, set the correct working directory

135. By Charles Kerr

sync with trunk

136. By Charles Kerr

fix FileNotFound cwd bug in keeper-template-test reported by Marcus

Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Looks good to me, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2016-07-05 21:09:03 +0000
3+++ debian/control 2016-07-21 10:16:32 +0000
4@@ -28,6 +28,10 @@
5 libdbustest1-dev,
6 libqtdbusmock1-dev (>= 0.4),
7 libqtdbustest1-dev,
8+# for the dbusmock template:
9+ python3-gi,
10+ gir1.2-glib-2.0 (>= 1.32),
11+ flake8,
12 Standards-Version: 3.9.5
13 Homepage: https://launchpad.net/keeper
14 # If you aren't a member of ~indicator-applet-developers but need to upload
15
16=== modified file 'src/qdbus-stubs/CMakeLists.txt'
17--- src/qdbus-stubs/CMakeLists.txt 2016-07-07 13:59:14 +0000
18+++ src/qdbus-stubs/CMakeLists.txt 2016-07-21 10:16:32 +0000
19@@ -115,9 +115,19 @@
20 #
21 #
22
23+set(
24+ STUBS_LIB
25+ qdbus-stubs
26+)
27+
28 add_library(
29- qdbus-stubs
30+ ${STUBS_LIB}
31 STATIC
32 ${interface_files}
33 ${adaptor_files}
34 )
35+
36+target_link_libraries(
37+ ${STUBS_LIB}
38+ backup-helper
39+)
40
41=== modified file 'src/qdbus-stubs/com.canonical.keeper.User.xml'
42--- src/qdbus-stubs/com.canonical.keeper.User.xml 2016-06-27 18:49:17 +0000
43+++ src/qdbus-stubs/com.canonical.keeper.User.xml 2016-07-21 10:16:32 +0000
44@@ -72,7 +72,7 @@
45 <doc:para>Provides state information so the user interface can show
46 the progress of backup or restore tasks.</doc:para>
47 <doc:para>State is a map of opaque backup keys to property maps,
48- which will contain a 'display-name' string and 'action' int
49+ which will contain a 'display-name' string and 'action' int32
50 whose possible values are queued(0), saving(1),
51 restoring(2), complete(3), stopped(4)</doc:para>
52 <doc:para>Some property maps may also have an 'item' string
53
54=== modified file 'tests/CMakeLists.txt'
55--- tests/CMakeLists.txt 2016-07-01 15:17:06 +0000
56+++ tests/CMakeLists.txt 2016-07-21 10:16:32 +0000
57@@ -1,11 +1,38 @@
58+###
59+### Build Google Test and Google Mock
60+###
61+
62 # We add -g so we get debug info for the gtest stack frames with gdb.
63-# The warnings are suppressed so we get a noise-free build for gtest and gmock.
64+# We add -w to supress warnings when compiling this external source code.
65 set(old_cxx_flags ${CMAKE_CXX_FLAGS})
66 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -w")
67 find_package(GMock REQUIRED)
68 find_package(Qt5Test REQUIRED)
69 set(CMAKE_CXX_FLAGS ${old_cxx_flags})
70
71+###
72+### Service DBusMock
73+###
74+
75+set(SERVICE_TEMPLATE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/com_canonical_keeper.py")
76+add_definitions(-DSERVICE_TEMPLATE_FILE="${SERVICE_TEMPLATE_FILE}")
77+
78+# get the python3 package directory
79+execute_process (
80+ COMMAND python3 -c "from distutils import sysconfig; print(sysconfig.get_python_lib())"
81+ COMMAND sed -r -e "s|/usr/(local/)?||g"
82+ OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
83+)
84+
85+install(
86+ FILES ${SERVICE_TEMPLATE_FILE}
87+ DESTINATION "${PYTHON_PACKAGE_DIR}/dbusmock/templates/"
88+)
89+
90+###
91+### The Tests
92+###
93+
94 pkg_check_modules(TEST_DEPENDENCIES
95 libqtdbustest-1 REQUIRED
96 libqtdbusmock-1 REQUIRED
97@@ -22,5 +49,6 @@
98 )
99
100 add_subdirectory(surface)
101+add_subdirectory(dbusmock)
102 add_subdirectory(unit)
103 add_subdirectory(utils)
104
105=== added file 'tests/com_canonical_keeper.py'
106--- tests/com_canonical_keeper.py 1970-01-01 00:00:00 +0000
107+++ tests/com_canonical_keeper.py 2016-07-21 10:16:32 +0000
108@@ -0,0 +1,435 @@
109+import copy
110+import dbus
111+import dbus.service
112+import os
113+import socket
114+import subprocess
115+import sys
116+from dbusmock import OBJECT_MANAGER_IFACE, mockobject
117+from gi.repository import GLib
118+
119+'''com.canonical.keeper mock template
120+'''
121+
122+# This program is free software; you can redistribute it and/or modify it under
123+# the terms of the GNU Lesser General Public License as published by the Free
124+# Software Foundation; either version 3 of the License, or (at your option) any
125+# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
126+# of the license.
127+
128+__author__ = 'Charles Kerr'
129+__email__ = 'charles.kerr@canonical.com'
130+__copyright__ = '(c) 2016 Canonical Ltd.'
131+__license__ = 'LGPL 3+'
132+
133+SERVICE_PATH = '/com/canonical/keeper'
134+SERVICE_IFACE = 'com.canonical.keeper'
135+USER_PATH = '/com/canonical/keeper/user'
136+USER_IFACE = 'com.canonical.keeper.User'
137+HELPER_PATH = '/com/canonical/keeper/helper'
138+HELPER_IFACE = 'com.canonical.keeper.Helper'
139+MOCK_IFACE = 'com.canonical.keeper.Mock'
140+
141+# magic keys used by dbusmock
142+BUS_NAME = 'com.canonical.keeper'
143+MAIN_IFACE = SERVICE_IFACE
144+MAIN_OBJ = SERVICE_PATH
145+SYSTEM_BUS = False
146+
147+ACTION_QUEUED = 0
148+ACTION_SAVING = 1
149+ACTION_RESTORING = 2
150+ACTION_COMPLETE = 3
151+ACTION_STOPPED = 4
152+
153+KEY_CTIME = 'ctime'
154+KEY_BLOB = 'blob-data'
155+KEY_HELPER = 'helper-exec'
156+KEY_ICON = 'icon'
157+KEY_NAME = 'display-name'
158+KEY_SIZE = 'size'
159+KEY_SUBTYPE = 'subtype'
160+KEY_TYPE = 'type'
161+KEY_UUID = 'uuid'
162+
163+TYPE_APP = 'application'
164+TYPE_FOLDER = 'folder'
165+TYPE_SYSTEM = 'system-data'
166+
167+#
168+# Utils
169+#
170+
171+
172+def badarg(msg):
173+ raise dbus.exceptions.DBusException(
174+ msg,
175+ name='org.freedesktop.DBus.Error.InvalidArgs'
176+ )
177+
178+
179+def fail(msg):
180+ raise dbus.exceptions.DBusException(
181+ msg,
182+ name='org.freedesktop.DBus.Error.Failed'
183+ )
184+
185+
186+def fail_if_busy():
187+ user = mockobject.objects[USER_PATH]
188+ if user.all_tasks:
189+ fail("can't do that while service is busy")
190+
191+
192+#
193+# User Obj
194+#
195+
196+
197+def user_get_backup_choices(user):
198+ return dbus.Dictionary(
199+ user.backup_choices,
200+ signature='sa{sv}',
201+ variant_level=1
202+ )
203+
204+
205+def user_get_restore_choices(user):
206+ return dbus.Dictionary(
207+ user.restore_choices,
208+ signature='sa{sv}',
209+ variant_level=1
210+ )
211+
212+
213+def user_start_next_task(user):
214+ if not user.remaining_tasks:
215+ user.log('last task finished')
216+ user.all_tasks = []
217+ user.current_task = None
218+ user.log('setting user.current_task to None')
219+ user.update_state_property(user)
220+
221+ else:
222+ uuid = user.remaining_tasks.pop(0)
223+ user.current_task = uuid
224+ user.update_state_property(user)
225+
226+ # find the helper to run
227+ choice = user.backup_choices.get(uuid)
228+ if not choice:
229+ choice = user.restore_choices.get(uuid)
230+ helper_exec = choice.get(KEY_HELPER)
231+
232+ # build the env that we'll pass to the helper
233+ henv = {}
234+ henv['QDBUS_DEBUG'] = '1'
235+ henv['G_DBUS_DEBUG'] = 'call,message,signal,return'
236+ for key in ['DBUS_SESSION_BUS_ADDRESS', 'DBUS_SYSTEM_BUS_ADDRESS']:
237+ val = os.environ.get(key, None)
238+ if val:
239+ henv[key] = val
240+
241+ # set the working directory for folder backups
242+ helper_cwd = os.getcwd()
243+ if choice.get(KEY_TYPE) == TYPE_FOLDER:
244+ helper_cwd = choice.get(KEY_SUBTYPE)
245+
246+ # spawn the helper
247+ user.log('starting %s for %s, env %s' % (helper_exec, uuid, henv))
248+ subprocess.Popen(
249+ [helper_exec, HELPER_PATH],
250+ env=henv, stdout=sys.stdout, stderr=sys.stderr,
251+ cwd=helper_cwd,
252+ shell=helper_exec.endswith('.sh')
253+ )
254+
255+
256+def user_start_backup(user, uuids):
257+
258+ # sanity checks
259+ fail_if_busy()
260+ for uuid in uuids:
261+ if uuid not in user.backup_choices:
262+ badarg('uuid %s is not a valid backup choice' % (uuid))
263+
264+ user.all_tasks = uuids
265+ user.remaining_tasks = copy.copy(uuids)
266+ user.start_next_task(user)
267+
268+
269+def user_start_restore(user, uuids):
270+
271+ # sanity checks
272+ fail_if_busy()
273+ for uuid in uuids:
274+ if uuid not in user.restore_choices:
275+ badarg('uuid %s is not a valid backup choice' % (uuid))
276+
277+ user.all_tasks = uuids
278+ user.remaining_tasks = copy.copy(uuids)
279+ user.start_next_task()
280+
281+
282+def user_cancel(user):
283+ # FIXME
284+ pass
285+
286+
287+def user_build_state(user):
288+ """Returns a generated state dictionary.
289+
290+ State is a map of opaque backup keys to property maps,
291+ which will contain a 'display-name' string and 'action' int
292+ whose possible values are queued(0), saving(1),
293+ restoring(2), complete(3), and stopped(4).
294+
295+ Some property maps may also have an 'item' string
296+ and a 'percent-done' double [0..1.0].
297+ For example if these are set to (1, "Photos", 0.36),
298+ the user interface could show "Backing up Photos (36%)".
299+ Clients should gracefully handle missing properties;
300+ eg a missing percent-done could instead show
301+ "Backing up Photos".
302+
303+ A failed task's property map may also contain an 'error'
304+ string if set by the backup helpers.
305+ """
306+
307+ tasks_states = {}
308+ for uuid in user.all_tasks:
309+ task_state = {}
310+
311+ # get the task's action
312+ if uuid == user.current_task:
313+ if uuid in user.backup_choices:
314+ action = ACTION_SAVING
315+ else:
316+ action = ACTION_RESTORING
317+ elif uuid in user.remaining_tasks:
318+ action = ACTION_QUEUED
319+ else: # FIXME: 'ACTION_STOPPED' not handled yet
320+ action = ACTION_COMPLETE
321+ task_state['action'] = dbus.Int32(action)
322+
323+ # get the task's display-name
324+ choice = user.backup_choices.get(uuid, None)
325+ if not choice:
326+ choice = user.restore_choices.get(uuid, None)
327+ if not choice:
328+ fail("couldn't find a choice for uuid %s" % (uuid))
329+ display_name = choice.get(KEY_NAME, None)
330+ task_state[KEY_NAME] = dbus.String(display_name)
331+
332+ # FIXME: use a real percentage here
333+ if action == ACTION_COMPLETE:
334+ percent_done = dbus.Double(1.0)
335+ elif action == ACTION_SAVING or action == ACTION_RESTORING:
336+ percent_done = dbus.Double(0.5)
337+ else:
338+ percent_done = dbus.Double(0.0)
339+ task_state['percent-done'] = percent_done
340+
341+ tasks_states[uuid] = dbus.Dictionary(task_state)
342+
343+ return dbus.Dictionary(
344+ tasks_states,
345+ signature='sa{sv}',
346+ variant_level=1
347+ )
348+
349+
350+def user_update_state_property(user):
351+ old_state = user.Get(USER_IFACE, 'State')
352+ new_state = user.build_state(user)
353+ if old_state != new_state:
354+ user.Set(USER_IFACE, 'State', new_state)
355+
356+
357+#
358+# Helper Obj
359+#
360+
361+class HelperWork:
362+ chunks = None
363+ n_bytes = None
364+ n_left = None
365+ sock = None
366+ uuid = None
367+
368+
369+def helper_periodic_func(helper):
370+
371+ if not helper.work:
372+ fail("bug: helper_periodic_func called w/o helper.work")
373+
374+ # try to read a bit
375+ chunk = helper.work.sock.recv(4096*2)
376+ if len(chunk):
377+ helper.work.chunks.append(chunk)
378+ helper.work.n_left -= len(chunk)
379+ helper.log('got %s bytes; %s left' % (len(chunk), helper.work.n_left))
380+
381+ # cleanup if done
382+ done = helper.work.n_left <= 0
383+ if done:
384+ helper.work.sock.shutdown(socket.SHUT_RDWR)
385+ helper.work.sock.close()
386+ user = mockobject.objects[USER_PATH]
387+ user.backup_data[helper.work.uuid] = b''.join(helper.work.chunks)
388+ user.log(
389+ 'backup %s done; %s bytes' %
390+ (helper.work.uuid, len(user.backup_data[helper.work.uuid]))
391+ )
392+ user.start_next_task(user)
393+ helper.work = None
394+
395+ if len(chunk) or done:
396+ user = mockobject.objects[USER_PATH]
397+ user.update_state_property(user)
398+
399+ return not done
400+
401+
402+def helper_start_backup(helper, n_bytes):
403+
404+ helper.log("got start_backup request for %s bytes" % (n_bytes))
405+ if helper.work:
406+ fail("can't start a new backup while one's already active")
407+
408+ parent, child = socket.socketpair()
409+
410+ # set up helper's workarea
411+ work = HelperWork()
412+ work.chunks = []
413+ work.n_bytes = n_bytes
414+ work.n_left = n_bytes
415+ work.sock = parent
416+ work.uuid = mockobject.objects[USER_PATH].current_task
417+ helper.work = work
418+
419+ # start checking periodically
420+ GLib.timeout_add(10, helper.periodic_func, helper)
421+ return dbus.types.UnixFd(child.fileno())
422+
423+
424+def helper_start_restore(helper):
425+ helper.parent, child = socket.socketpair()
426+ return child
427+
428+
429+#
430+# Controlling the mock
431+#
432+
433+
434+def mock_add_backup_choice(mock, uuid, props):
435+
436+ keys = [KEY_NAME, KEY_TYPE, KEY_SUBTYPE, KEY_ICON, KEY_HELPER]
437+ if set(keys) != set(props.keys()):
438+ badarg('need props: %s got %s' % (keys, props.keys()))
439+
440+ user = mockobject.objects[USER_PATH]
441+ user.backup_choices[uuid] = dbus.Dictionary(
442+ props,
443+ signature='sv',
444+ variant_level=1
445+ )
446+
447+
448+def mock_add_restore_choice(mock, uuid, props):
449+
450+ keys = [KEY_NAME, KEY_TYPE, KEY_SUBTYPE, KEY_ICON, KEY_HELPER,
451+ KEY_SIZE, KEY_CTIME, KEY_BLOB]
452+ if set(keys) != set(props.keys()):
453+ badarg('need props: %s got %s' % (keys, props.keys()))
454+
455+ user = mockobject.objects[USER_PATH]
456+ user.restore_choices[uuid] = dbus.Dictionary(
457+ props,
458+ signature='sv',
459+ variant_level=1
460+ )
461+
462+
463+def mock_get_backup_data(mock, uuid):
464+ blob = mockobject.objects[USER_PATH].backup_data[uuid]
465+ mock.log('returning %s byte blob for uuid %s' % (len(blob), uuid))
466+ return blob
467+
468+
469+#
470+#
471+#
472+
473+def load(main, parameters):
474+
475+ main.log('Keeper template paramers: "' + str(parameters) + '"')
476+
477+ # com.canonical.keeper.User
478+ path = USER_PATH
479+ main.AddObject(path, USER_IFACE, {}, [])
480+ o = mockobject.objects[path]
481+ o.get_backup_choices = user_get_backup_choices
482+ o.start_backup = user_start_backup
483+ o.get_restore_choices = user_get_restore_choices
484+ o.start_restore = user_start_restore
485+ o.cancel = user_cancel
486+ o.build_state = user_build_state
487+ o.update_state_property = user_update_state_property
488+ o.start_next_task = user_start_next_task
489+ o.all_tasks = []
490+ o.remaining_tasks = []
491+ o.backup_data = {}
492+ o.backup_choices = parameters.get('backup-choices', {})
493+ o.restore_choices = parameters.get('restore-choices', {})
494+ o.current_task = None
495+ o.defined_types = [TYPE_APP, TYPE_SYSTEM, TYPE_FOLDER]
496+ o.AddMethods(USER_IFACE, [
497+ ('GetBackupChoices', '', 'a{sa{sv}}',
498+ 'ret = self.get_backup_choices(self)'),
499+ ('StartBackup', 'as', '',
500+ 'self.start_backup(self, args[0])'),
501+ ('GetRestoreChoices', '', 'a{sa{sv}}',
502+ 'ret = self.get_restore_choices(self)'),
503+ ('StartRestore', 'as', '',
504+ 'self.start_restore(self, args[0])'),
505+ ('Cancel', '', '',
506+ 'self.cancel(self)'),
507+ ])
508+ o.AddProperty(USER_IFACE, "State", o.build_state(o))
509+
510+ # com.canonical.keeper.Helper
511+ path = HELPER_PATH
512+ main.AddObject(path, HELPER_IFACE, {}, [])
513+ o = mockobject.objects[path]
514+ o.start_backup = helper_start_backup
515+ o.start_restore = helper_start_restore
516+ o.periodic_func = helper_periodic_func
517+ o.work = None
518+ o.AddMethods(HELPER_IFACE, [
519+ ('StartBackup', 't', 'h',
520+ 'ret = self.start_backup(self, args[0])'),
521+ ('StartRestore', '', 'h',
522+ 'ret = self.start_restore(self)')
523+ ])
524+
525+ # com.canonical.keeper.Mock
526+ o = main
527+ o.add_backup_choice = mock_add_backup_choice
528+ o.add_restore_choice = mock_add_restore_choice
529+ o.get_backup_data = mock_get_backup_data
530+ o.AddMethods(MOCK_IFACE, [
531+ ('AddBackupChoice', 'sa{sv}', '',
532+ 'self.add_backup_choice(self, args[0], args[1])'),
533+ ('AddRestoreChoice', 'sa{sv}', '',
534+ 'self.add_restore_choice(self, args[0], args[1])'),
535+ ('GetBackupData', 's', 'ay',
536+ 'ret = self.get_backup_data(self, args[0])')
537+ ])
538+ o.EmitSignal(
539+ OBJECT_MANAGER_IFACE,
540+ 'InterfacesAdded',
541+ 'oa{sa{sv}}',
542+ [SERVICE_PATH, {MOCK_IFACE: {}}]
543+ )
544
545=== added directory 'tests/dbusmock'
546=== added file 'tests/dbusmock/CMakeLists.txt'
547--- tests/dbusmock/CMakeLists.txt 1970-01-01 00:00:00 +0000
548+++ tests/dbusmock/CMakeLists.txt 2016-07-21 10:16:32 +0000
549@@ -0,0 +1,62 @@
550+
551+include_directories(
552+ ${CMAKE_SOURCE_DIR}/src
553+ ${CMAKE_BINARY_DIR}/src
554+)
555+
556+set(
557+ LINK_LIBS
558+ qdbus-stubs
559+ ${TEST_DEPENDENCIES_LDFLAGS}
560+ test-utils
561+ Qt5::Core
562+ Qt5::DBus
563+ Qt5::Network
564+ ${GTEST_LIBRARIES}
565+ ${GMOCK_LIBRARIES}
566+)
567+
568+###
569+###
570+
571+set(
572+ BACKUP_HELPER
573+ fake-backup-helper
574+)
575+
576+add_executable(
577+ ${BACKUP_HELPER}
578+ fake-backup-helper.cpp
579+)
580+target_link_libraries(
581+ ${BACKUP_HELPER}
582+ ${LINK_LIBS}
583+)
584+
585+###
586+###
587+
588+set(
589+ TEST_NAME
590+ keeper-template-test
591+)
592+
593+add_executable(
594+ ${TEST_NAME}
595+ keeper-template-test.cpp
596+)
597+
598+set_property(
599+ SOURCE keeper-template-test.cpp
600+ PROPERTIES APPEND_STRING PROPERTY COMPILE_DEFINITIONS FAKE_BACKUP_HELPER_EXEC=\"${CMAKE_CURRENT_BINARY_DIR}/${BACKUP_HELPER}\"
601+)
602+
603+target_link_libraries(
604+ ${TEST_NAME}
605+ ${LINK_LIBS}
606+)
607+
608+add_test(
609+ ${TEST_NAME}
610+ ${TEST_NAME}
611+)
612
613=== added file 'tests/dbusmock/fake-backup-helper.cpp'
614--- tests/dbusmock/fake-backup-helper.cpp 1970-01-01 00:00:00 +0000
615+++ tests/dbusmock/fake-backup-helper.cpp 2016-07-21 10:16:32 +0000
616@@ -0,0 +1,63 @@
617+/*
618+ * Copyright (C) 2016 Canonical, Ltd.
619+ *
620+ * This program is free software: you can redistribute it and/or modify it
621+ * under the terms of the GNU General Public License version 3, as published
622+ * by the Free Software Foundation.
623+ *
624+ * This program is distributed in the hope that it will be useful, but
625+ * WITHOUT ANY WARRANTY; without even the implied warranties of
626+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
627+ * PURPOSE. See the GNU General Public License for more details.
628+ *
629+ * You should have received a copy of the GNU General Public License along
630+ * with this program. If not, see <http://www.gnu.org/licenses/>.
631+ *
632+ * Authors:
633+ * Charles Kerr <charles.kerr@canonical.com>
634+ */
635+
636+#include "fake-backup-helper.h"
637+
638+#include <qdbus-stubs/dbus-types.h>
639+#include <qdbus-stubs/keeper_helper_interface.h>
640+
641+#include <QCoreApplication>
642+#include <QDebug>
643+#include <QDBusConnection>
644+#include <QDBusInterface>
645+#include <QDBusReply>
646+#include <QProcessEnvironment>
647+#include <QLocalSocket>
648+
649+int
650+main(int argc, char **argv)
651+{
652+ QCoreApplication app(argc, argv);
653+
654+ const auto blob = QByteArray(FAKE_BACKUP_HELPER_PAYLOAD);
655+
656+ // dump the inputs to stdout
657+ qDebug() << "argc:" << argc;
658+ for(int i=0; i<argc; ++i)
659+ qDebug() << "argv[" << i << "] is" << argv[i];
660+ const auto env = QProcessEnvironment::systemEnvironment();
661+ for(const auto& key : env.keys())
662+ qDebug() << "env" << qPrintable(key) << "is" << qPrintable(env.value(key));
663+
664+ // ask the service for a socket
665+ auto conn = QDBusConnection::connectToBus(QDBusConnection::SessionBus, DBusTypes::KEEPER_SERVICE);
666+ const auto object_path = QString::fromUtf8(argv[1]);
667+ DBusInterfaceKeeperHelper helper_iface (DBusTypes::KEEPER_SERVICE, object_path, conn);
668+ QDBusReply<QDBusUnixFileDescriptor> reply = helper_iface.call("StartBackup", blob.size());
669+ const auto ufd = reply.value();
670+ Q_ASSERT(reply.isValid());
671+
672+ // write the blob
673+ QLocalSocket sock;
674+ sock.setSocketDescriptor(ufd.fileDescriptor());
675+ qDebug() << "wrote" << sock.write(blob) << "bytes";
676+ sock.flush();
677+
678+ return EXIT_SUCCESS;
679+}
680
681=== added file 'tests/dbusmock/fake-backup-helper.h'
682--- tests/dbusmock/fake-backup-helper.h 1970-01-01 00:00:00 +0000
683+++ tests/dbusmock/fake-backup-helper.h 2016-07-21 10:16:32 +0000
684@@ -0,0 +1,38 @@
685+/*
686+ * Copyright (C) 2016 Canonical, Ltd.
687+ *
688+ * This program is free software: you can redistribute it and/or modify it
689+ * under the terms of the GNU General Public License version 3, as published
690+ * by the Free Software Foundation.
691+ *
692+ * This program is distributed in the hope that it will be useful, but
693+ * WITHOUT ANY WARRANTY; without even the implied warranties of
694+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
695+ * PURPOSE. See the GNU General Public License for more details.
696+ *
697+ * You should have received a copy of the GNU General Public License along
698+ * with this program. If not, see <http://www.gnu.org/licenses/>.
699+ *
700+ * Authors:
701+ * Charles Kerr <charles.kerr@canonical.com>
702+ */
703+
704+#pragma once
705+
706+static const char* FAKE_BACKUP_HELPER_PAYLOAD = R"(
707+ Oh mmm I know a place
708+ Ain't nobody cryin'
709+ Ain't nobody worried
710+ Ain't no smilin' faces
711+ Mmm, no no
712+ Lyin' to the races
713+ Help me, come on, come on
714+ Somebody, help me now (I'll take you there)
715+ Help me, ya'all (I'll take you there)
716+ Help me now (I'll take you there)
717+ Oh! (I'll take you there)
718+ Oh! Oh! Mercy! (I'll take you there)
719+ Oh, let me take you there (I'll take you there)
720+ Oh-oh! Let me take you there! (I'll take you there)
721+ Play your, play your piano now
722+)";
723
724=== added file 'tests/dbusmock/keeper-template-test.cpp'
725--- tests/dbusmock/keeper-template-test.cpp 1970-01-01 00:00:00 +0000
726+++ tests/dbusmock/keeper-template-test.cpp 2016-07-21 10:16:32 +0000
727@@ -0,0 +1,285 @@
728+/*
729+ * Copyright 2016 Canonical Ltd.
730+ *
731+ * This program is free software: you can redistribute it and/or modify it
732+ * under the terms of the GNU General Public License version 3, as published
733+ * by the Free Software Foundation.
734+ *
735+ * This program is distributed in the hope that it will be useful, but
736+ * WITHOUT ANY WARRANTY; without even the implied warranties of
737+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
738+ * PURPOSE. See the GNU General Public License for more details.
739+ *
740+ * You should have received a copy of the GNU General Public License along
741+ * with this program. If not, see <http://www.gnu.org/licenses/>.
742+ *
743+ * Authors:
744+ * Charles Kerr <charles.kerr@canonical.com>
745+ */
746+
747+#include "fake-backup-helper.h"
748+
749+#include "qdbus-stubs/dbus-types.h"
750+#include "qdbus-stubs/keeper_interface.h"
751+#include "qdbus-stubs/keeper_user_interface.h"
752+
753+#include <gtest/gtest.h>
754+
755+#include <libqtdbusmock/DBusMock.h>
756+#include <libqtdbustest/DBusTestRunner.h>
757+
758+#include <QDBusConnection>
759+#include <QDebug>
760+#include <QString>
761+#include <QThread>
762+#include <QUuid>
763+#include <QVariant>
764+
765+#include <memory>
766+
767+#define KEY_ACTION "action"
768+#define KEY_CTIME "ctime"
769+#define KEY_BLOB "blob-data"
770+#define KEY_HELPER "helper-exec"
771+#define KEY_ICON "icon"
772+#define KEY_NAME "display-name"
773+#define KEY_PERCENT "percent-done"
774+#define KEY_SIZE "size"
775+#define KEY_SUBTYPE "subtype"
776+#define KEY_TYPE "type"
777+#define KEY_UUID "uuid"
778+
779+// FIXME: this should go in a shared header
780+enum
781+{
782+ ACTION_QUEUED = 0,
783+ ACTION_SAVING = 1,
784+ ACTION_RESTORING = 2,
785+ ACTION_IDLE = 3
786+};
787+
788+using namespace QtDBusMock;
789+using namespace QtDBusTest;
790+
791+
792+class KeeperTemplateTest : public ::testing::Test
793+{
794+protected:
795+
796+ virtual void SetUp() override
797+ {
798+ test_runner_.reset(new DBusTestRunner{});
799+
800+ dbus_mock_.reset(new DBusMock(*test_runner_));
801+ dbus_mock_->registerTemplate(
802+ DBusTypes::KEEPER_SERVICE,
803+ SERVICE_TEMPLATE_FILE,
804+ QDBusConnection::SessionBus
805+ );
806+
807+ test_runner_->startServices();
808+ auto conn = connection();
809+ qDebug() << "session bus is" << qPrintable(conn.name());
810+
811+ keeper_iface_.reset(
812+ new DBusInterfaceKeeper(
813+ DBusTypes::KEEPER_SERVICE,
814+ DBusTypes::KEEPER_SERVICE_PATH,
815+ conn
816+ )
817+ );
818+ ASSERT_TRUE(keeper_iface_->isValid()) << qPrintable(conn.lastError().message());
819+
820+ user_iface_.reset(
821+ new DBusInterfaceKeeperUser(
822+ DBusTypes::KEEPER_SERVICE,
823+ DBusTypes::KEEPER_USER_PATH,
824+ conn
825+ )
826+ );
827+ ASSERT_TRUE(user_iface_->isValid()) << qPrintable(conn.lastError().message());
828+
829+ mock_iface_.reset(
830+ new QDBusInterface(
831+ DBusTypes::KEEPER_SERVICE,
832+ DBusTypes::KEEPER_SERVICE_PATH,
833+ QStringLiteral("com.canonical.keeper.Mock"),
834+ conn
835+ )
836+ );
837+ ASSERT_TRUE(mock_iface_->isValid()) << qPrintable(conn.lastError().message());
838+ }
839+
840+ virtual void TearDown() override
841+ {
842+ user_iface_.reset();
843+ keeper_iface_.reset();
844+ dbus_mock_.reset();
845+ test_runner_.reset();
846+ }
847+
848+ const QDBusConnection& connection()
849+ {
850+ return test_runner_->sessionConnection();
851+ }
852+
853+ std::unique_ptr<DBusTestRunner> test_runner_;
854+ std::unique_ptr<DBusMock> dbus_mock_;
855+ std::unique_ptr<DBusInterfaceKeeper> keeper_iface_;
856+ std::unique_ptr<DBusInterfaceKeeperUser> user_iface_;
857+ std::unique_ptr<QDBusInterface> mock_iface_;
858+
859+ void EXPECT_EVENTUALLY(std::function<bool()>&& test, qint64 timeout_msec=5000)
860+ {
861+ QElapsedTimer timer;
862+ timer.start();
863+ bool passed;
864+ do {
865+ passed = test();
866+ if (!passed)
867+ QThread::msleep(100);
868+ } while (!passed && !timer.hasExpired(timeout_msec));
869+ EXPECT_TRUE(passed);
870+ }
871+
872+ void wait_for_backup_to_finish()
873+ {
874+ EXPECT_EVENTUALLY([this]{return !user_iface_->state().isEmpty();}); // backup running
875+ EXPECT_EVENTUALLY([this]{return user_iface_->state().isEmpty();}); // backup finished
876+ }
877+};
878+
879+
880+/***
881+**** Quick surface-level tests
882+***/
883+
884+
885+// test that the dbusmock scaffolding starts up and exits ok
886+TEST_F(KeeperTemplateTest, HelloWorld)
887+{
888+}
889+
890+
891+// test GetBackupChoices() returns what we give to AddBackupChoice()
892+TEST_F(KeeperTemplateTest, BackupChoices)
893+{
894+ // build a backup choice
895+ const auto uuid = QUuid::createUuid().toString();
896+ const QMap<QString,QVariant> props {
897+ { KEY_NAME, QStringLiteral("some-name") },
898+ { KEY_TYPE, QStringLiteral("some-type") },
899+ { KEY_SUBTYPE, QStringLiteral("some-subtype") },
900+ { KEY_ICON, QStringLiteral("some-icon") },
901+ { KEY_HELPER, QString::fromUtf8(FAKE_BACKUP_HELPER_EXEC) }
902+ };
903+
904+ // add it
905+ auto msg = mock_iface_->call(QStringLiteral("AddBackupChoice"), uuid, props);
906+ EXPECT_NE(QDBusMessage::ErrorMessage, msg.type()) << qPrintable(msg.errorMessage());
907+
908+ // ask for a list of backup choices
909+ QDBusReply<QVariantDictMap> choices = user_iface_->call("GetBackupChoices");
910+ EXPECT_TRUE(choices.isValid()) << qPrintable(choices.error().message());
911+
912+ // check the results
913+ const auto expected_choices = QVariantDictMap{{uuid, props}};
914+ ASSERT_EQ(expected_choices, choices);
915+}
916+
917+
918+// test that StartBackup() fails if we pass an invalid arg
919+TEST_F(KeeperTemplateTest, StartBackupWithInvalidArg)
920+{
921+ const auto invalid_uuid = QUuid::createUuid().toString();
922+
923+ QDBusReply<void> reply = user_iface_->call("StartBackup", QStringList{invalid_uuid});
924+ EXPECT_FALSE(reply.isValid());
925+ EXPECT_EQ(QDBusError::InvalidArgs, reply.error().type());
926+}
927+
928+
929+// test GetRestoreChoices() returns what we give to AddRestoreChoice()
930+TEST_F(KeeperTemplateTest, RestoreChoices)
931+{
932+ // build a restore choice
933+ const auto uuid = QUuid::createUuid().toString();
934+ const auto blob = QUuid::createUuid().toByteArray();
935+ const QMap<QString,QVariant> props {
936+ { KEY_NAME, QStringLiteral("some-name") },
937+ { KEY_TYPE, QStringLiteral("some-type") },
938+ { KEY_SUBTYPE, QStringLiteral("some-subtype") },
939+ { KEY_ICON, QStringLiteral("some-icon") },
940+ { KEY_HELPER, QString::fromUtf8("/dev/null") },
941+ { KEY_SIZE, quint64(blob.size()) },
942+ { KEY_CTIME, quint64(time(nullptr)) },
943+ { KEY_BLOB, blob }
944+ };
945+
946+ // add it
947+ auto msg = mock_iface_->call(QStringLiteral("AddRestoreChoice"), uuid, props);
948+ EXPECT_NE(QDBusMessage::ErrorMessage, msg.type()) << qPrintable(msg.errorMessage());
949+
950+ // ask for a list of restore choices
951+ QDBusReply<QVariantDictMap> choices = user_iface_->call("GetRestoreChoices");
952+ EXPECT_TRUE(choices.isValid()) << qPrintable(choices.error().message());
953+
954+ // check the results
955+ const auto expected_choices = QVariantDictMap{{uuid, props}};
956+ ASSERT_EQ(expected_choices, choices);
957+}
958+
959+
960+// test that StartRestore() fails if we pass an invalid arg
961+TEST_F(KeeperTemplateTest, StartRestoreWithInvalidArg)
962+{
963+ const auto invalid_uuid = QUuid::createUuid().toString();
964+
965+ QDBusReply<void> reply = user_iface_->call("StartRestore", QStringList{invalid_uuid});
966+ EXPECT_FALSE(reply.isValid());
967+ EXPECT_EQ(QDBusError::InvalidArgs, reply.error().type());
968+}
969+
970+
971+// test that Status() returns empty if we haven't done anything yet
972+TEST_F(KeeperTemplateTest, TestEmptyStatus)
973+{
974+ EXPECT_TRUE(user_iface_->state().isEmpty());
975+}
976+
977+
978+/***
979+**** Make a real backup
980+***/
981+
982+TEST_F(KeeperTemplateTest, BackupRun)
983+{
984+ QTemporaryDir sandbox;
985+
986+ // build a backup choice
987+ const auto uuid = QUuid::createUuid().toString();
988+ const QMap<QString,QVariant> props {
989+ { KEY_NAME, QStringLiteral("Music") },
990+ { KEY_TYPE, QStringLiteral("folder") },
991+ { KEY_SUBTYPE, sandbox.path() },
992+ { KEY_ICON, QStringLiteral("music-icon") },
993+ { KEY_HELPER, QString::fromUtf8(FAKE_BACKUP_HELPER_EXEC) }
994+ };
995+
996+ // add it
997+ auto msg = mock_iface_->call(QStringLiteral("AddBackupChoice"), uuid, props);
998+ EXPECT_NE(QDBusMessage::ErrorMessage, msg.type()) << qPrintable(msg.errorMessage());
999+
1000+ // start the backup
1001+ QDBusReply<void> reply = user_iface_->call("StartBackup", QStringList{uuid});
1002+ EXPECT_TRUE(reply.isValid()) << qPrintable(reply.error().message());
1003+ wait_for_backup_to_finish();
1004+
1005+ // ask keeper for the blob
1006+ QDBusReply<QByteArray> blob = mock_iface_->call(QStringLiteral("GetBackupData"), uuid);
1007+ EXPECT_TRUE(reply.isValid()) << qPrintable(reply.error().message());
1008+
1009+ // check the results
1010+ const auto expected_blob = QByteArray(FAKE_BACKUP_HELPER_PAYLOAD);
1011+ ASSERT_EQ(expected_blob, blob);
1012+}
1013
1014=== modified file 'tests/surface/CMakeLists.txt'
1015--- tests/surface/CMakeLists.txt 2016-06-25 18:46:42 +0000
1016+++ tests/surface/CMakeLists.txt 2016-07-21 10:16:32 +0000
1017@@ -1,4 +1,5 @@
1018 add_test(cppcheck cppcheck --enable=all -USCHEMA_DIR --error-exitcode=2 --inline-suppr -I${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests)
1019+add_test(flake8 flake8 ${SERVICE_TEMPLATE_FILE})
1020
1021 add_subdirectory(copyright)
1022 add_subdirectory(whitespace)
1023
1024=== modified file 'tests/unit/helper/CMakeLists.txt'
1025--- tests/unit/helper/CMakeLists.txt 2016-07-15 14:13:54 +0000
1026+++ tests/unit/helper/CMakeLists.txt 2016-07-21 10:16:32 +0000
1027@@ -1,5 +1,3 @@
1028-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
1029-
1030 add_definitions ( -DCMAKE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
1031 add_definitions ( -DCMAKE_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}" )
1032
1033
1034=== modified file 'tests/utils/CMakeLists.txt'
1035--- tests/utils/CMakeLists.txt 2016-07-11 17:04:16 +0000
1036+++ tests/utils/CMakeLists.txt 2016-07-21 10:16:32 +0000
1037@@ -1,9 +1,16 @@
1038
1039-include_directories("${CMAKE_SOURCE_DIR}/src")
1040-include_directories(${CMAKE_CURRENT_BINARY_DIR})
1041+include_directories(
1042+ ${CMAKE_SOURCE_DIR}/src
1043+ ${CMAKE_CURRENT_BINARY_DIR}
1044+)
1045+
1046+set(
1047+ LIB_NAME
1048+ test-utils
1049+)
1050
1051 add_library(
1052- test-utils
1053+ ${LIB_NAME}
1054 STATIC
1055 main.cpp
1056 dummy-file.cpp
1057@@ -11,6 +18,6 @@
1058 )
1059
1060 qt5_use_modules(
1061- test-utils
1062+ ${LIB_NAME}
1063 Core
1064 )
1065
1066=== modified file 'tests/utils/main.cpp'
1067--- tests/utils/main.cpp 2016-07-01 18:02:27 +0000
1068+++ tests/utils/main.cpp 2016-07-21 10:16:32 +0000
1069@@ -17,6 +17,8 @@
1070 * Pete Woods <pete.woods@canonical.com>
1071 */
1072
1073+#include "qdbus-stubs/dbus-types.h"
1074+
1075 #include <libqtdbusmock/DBusMock.h>
1076
1077 #include <gtest/gtest.h>
1078@@ -51,6 +53,8 @@
1079
1080 QCoreApplication application(argc, argv);
1081 DBusMock::registerMetaTypes();
1082+ DBusTypes::registerMetaTypes();
1083+
1084 ::testing::InitGoogleTest(&argc, argv);
1085
1086 Runner runner;

Subscribers

People subscribed via source and target branches

to all changes: