1
# Copyright 2008-2015 Canonical
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU Affero General Public License as
5
# published by the Free Software Foundation, either version 3 of the
6
# License, or (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU Affero General Public License for more details.
13
# You should have received a copy of the GNU Affero General Public License
14
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
# For further info, check http://launchpad.net/filesync-server
18
"""Integration Tests for UDFs."""
20
import multiprocessing
29
from optparse import OptionParser
32
import dbus.mainloop.glib # this is black magic. DO NOT REMOVE!
34
from distutils.spawn import find_executable
36
# fix paths 1, to include lib for most basic stuff
37
LIB_DIR = os.path.abspath("lib")
38
sys.path.insert(0, LIB_DIR)
40
from twisted.internet import glib2reactor
41
glib2reactor.install() # before any reactor import
43
from ubuntuone.storage.server.testing.testcase import create_test_user
44
from utilities import utils, dev_launcher
45
from ubuntuone.platform.linux import tools
46
from twisted.internet import reactor, defer
48
from ubuntuone.storage.tests.integration.helpers import debug, retryable
51
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
54
# this should be done manually before:
59
cd sourcecode/ubuntuone-client/
60
./autogen.sh --with-protocol=../ubuntuone-storage-protocol/
65
ROOT = utils.get_rootdir()
66
TMP_DIR = os.path.join(ROOT, 'tmp')
67
PID_FILENAME = os.path.join(TMP_DIR, 'syncdaemon-process-%d.pid')
69
_dbus = find_executable("dbus-daemon")
71
raise RuntimeError("dbus-daemon was not found.")
75
"""Start our own session bus daemon for testing."""
76
config_file = os.path.join(os.getcwd(), "configs", "dbus-session.conf")
77
dbus_args = ["--fork",
78
"--config-file=" + config_file,
81
p = subprocess.Popen([_dbus] + dbus_args, bufsize=4096,
82
stdout=subprocess.PIPE,
83
stderr=subprocess.PIPE)
85
dbus_address = "".join(p.stdout.readlines()).strip()
86
dbus_pid = int("".join(p.stderr.readlines()).strip())
88
if dbus_address == "":
89
os.kill(dbus_pid, signal.SIGKILL)
90
raise Exception("There was a problem launching dbus-daemon.")
92
return dbus_pid, dbus_address
96
"""Check if we're ready to go.
98
This tests the best way we can that all dependencies are ready. It depends
99
on how other parts of the projects do their stuff, so it may change.
101
couchdb_port = os.path.join(TMP_DIR, "couchdb-master0.port")
102
if not os.path.exists(couchdb_port):
103
print "Not ready! Hint: did you do...?:\n" + DEP_STARTOAUTH
106
log_conf = os.path.join(ROOT, "sourcecode",
107
"ubuntuone-client", "data", "logging.conf")
108
if not os.path.exists(log_conf):
109
print "Not ready! Hint: did you do...?:\n" + DEP_BUILDCLIENT
113
def create_syncdaemon(username, procnum, homedir, pidfile):
114
"""Creates a SD with its DBus."""
115
prefix = 'Create_syncdaemon:'
117
debug(prefix, 'Starting DBus...')
118
(dbus_pid, dbus_address) = start_DBus()
119
debug(prefix, 'DBus started with (dbus_pid, dbus_address):',
120
dbus_pid, dbus_address)
123
env = dict(DBUS_SESSION_BUS_ADDRESS=dbus_address,
125
XDG_CACHE_HOME="tmp/xdg_cache%d" % procnum)
126
value = (dbus_pid, dbus_address)
127
debug(prefix, "Putting in pidfile %s values %s" % (pidfile, value))
128
with open(pidfile, 'w') as f:
129
f.write(' '.join(map(str, value)))
132
debug(prefix, "Launching SD with dbus", dbus_address)
133
dev_launcher.launch("sourcecode/ubuntuone-client/bin/ubuntuone-syncdaemon",
134
username, params=("--send_events_over_dbus",
135
"--udf_autosubscribe=true"),
136
environ=env, homedir=homedir, verbose=True)
139
class SyncDaemonToolProxy(object):
140
"""Call SDT methods, but retrying."""
142
def __init__(self, conn, event_rcvr):
143
self._sdt = tools.SyncDaemonTool(conn)
144
self._sdt.bus.add_signal_receiver(event_rcvr, signal_name='Event')
146
def __getattr__(self, name):
147
"""Return SDT method wrapped in retryable."""
148
original_method = getattr(self._sdt, name)
149
return retryable(original_method)
152
class SyncDaemon(object):
153
"""SyncDaemon abstraction."""
155
def __init__(self, username, procnum, timestamp, verbose=False):
157
self._dbus_pid = None
158
self._dbus_address = None
159
self.username = username
160
self.procnum = procnum
161
self.verbose = verbose
162
self.prefix = 'SyncDaemon %d:' % procnum
163
sd_home = 'syncdaemon-home-%d-%s' % (procnum, timestamp)
164
self.homedir = os.path.join(TMP_DIR, sd_home, username)
165
self.rootdir = os.path.join(self.homedir, 'Ubuntu One')
166
self.pidfile = PID_FILENAME % procnum
170
if os.path.exists(self.pidfile):
171
os.remove(self.pidfile)
173
def debug(self, msg, *args):
174
"""Print debug messages."""
176
debug(self.prefix, msg, *args)
178
def parse_daemon_data(self):
179
"""Get dbus_pid, dbsu_address and homedir for process."""
181
if os.path.exists(self.pidfile):
183
self.debug("Process' pid file doesn't exist, sleeping 2 second",
187
ValueError("ERROR: couldn't see the pidfile: %r" % (self.pidfile,))
189
with open(self.pidfile) as f:
192
return content.split()
194
def record_event(self, event_dict):
195
"""Store events for further analysis."""
196
self.events.append(event_dict)
198
@defer.inlineCallbacks
200
"""Connects the syncdaemon."""
201
self.debug("Starting SyncDaemon", self.procnum)
202
args = (self.username, self.procnum, self.homedir, self.pidfile)
203
self._proc = multiprocessing.Process(target=create_syncdaemon,
205
self.debug("Process created:", self._proc)
207
self.debug("Process started.")
209
self._dbus_pid, self._dbus_address = self.parse_daemon_data()
210
self._dbus_pid = int(self._dbus_pid)
211
self.debug("Daemon data parsed, values:",
212
self._dbus_pid, self._dbus_address)
214
conn = dbus.bus.BusConnection(self._dbus_address)
215
self.debug("dbus.bus.BusConnection coon:", conn)
216
self.sdt = SyncDaemonToolProxy(conn, self.record_event)
217
self.debug("tools.SyncDaemonTool:", self.sdt._sdt)
219
# let's wait for it to start, connect and wait for nirvana
220
self.debug("Let's connect! events so far", self.events)
221
yield self.sdt.connect()
222
self.debug("Connected, waiting for nirvana now.")
223
yield self.sdt.wait_for_nirvana(1)
226
"""Kills the running syncdaemon."""
227
self.debug("Stopping SyncDaemon", self.procnum)
228
assert self.sdt._sdt is not None
230
d.addCallback(lambda _: os.kill(self._dbus_pid, signal.SIGKILL))
231
d.addCallback(lambda _: os.remove(self.pidfile))
234
def wait_for_event(self, event_name, **event_args):
235
"""Wait for a given event."""
236
self.debug('Waiting for event', event_name)
239
"""Filter an event."""
240
if event['event_name'] != event_name:
242
for key, value in event_args:
243
if event[key] != value:
247
return self.sdt.wait_for_signal('Event', filter)
250
def get_all_tests(test_filter):
251
"""Collect all tests to execute."""
252
# convert the test filter to an usable dict
254
for t in test_filter:
256
fmod, ftest = t.split(".")
257
testsok.setdefault(fmod, []).append(ftest)
261
base = os.path.dirname(__file__)
262
test_files = [x for x in os.listdir(base)
263
if x.startswith('integtests_') and x.endswith('.py')]
266
for fname in test_files:
268
mod = __import__(modname)
269
funcnames = [x for x in dir(mod) if x.startswith('test_')]
271
# check if in asked for tests
272
funcnames_filtered = []
274
# nothing required, all in
275
funcnames_filtered = funcnames
277
if modname in testsok:
278
tests = testsok[modname]
280
# all the module is ok
281
funcnames_filtered = funcnames
283
funcnames_filtered = [n for n in funcnames if n in tests]
285
funcs = [getattr(mod, x) for x in funcnames_filtered]
286
all_funcs.extend(funcs)
288
random.shuffle(all_funcs)
292
@defer.inlineCallbacks
293
def execute_tests(all_tests, sd1, sd2, sd3):
294
"""Execute the whole suite, please."""
295
prefix = 'Execute tests:'
297
debug(prefix, 'Starting')
298
assert os.path.exists(sd1.rootdir), sd1.rootdir + ' must exist'
300
# create directory and wait for it to go all places
301
dir_in_sd1 = sd1.wait_for_event('AQ_DIR_NEW_OK')
302
dir_in_sd2 = sd1.wait_for_event('FS_DIR_CREATE')
303
os.mkdir(os.path.join(sd1.rootdir, ".mark"))
305
debug(prefix, 'AQ_DIR_NEW_OK in sd1 done.')
307
debug(prefix, 'FS_DIR_CREATE in sd2 done.')
310
yield sd1.sdt.wait_for_nirvana(.5)
311
yield sd2.sdt.wait_for_nirvana(.5)
312
debug(prefix, 'Nirvana reached for sd1 and sd2.')
314
len_tests = len(all_tests)
315
for i, test in enumerate(all_tests):
316
test_name = test.func_name
317
testprefix = ' '.join(("===", test_name, "==="))
319
skipit = getattr(test, 'skip', None)
321
debug(testprefix, 'Skipping test!', skipit, previous_newline=True)
324
debug(testprefix, 'Starting test %d of %d' % (i + 1, len_tests),
325
previous_newline=True)
328
yield test(test_name, sd1, sd2, sd3, testprefix)
330
debug(testprefix, 'Crushing failure and despair, :( -- ', str(e))
333
debug(testprefix, 'Test finished ok! :)')
335
debug(testprefix, 'Cleaning up.')
337
# wait for SDs to finish
338
yield sd1.sdt.wait_for_nirvana(.5)
339
yield sd2.sdt.wait_for_nirvana(.5)
340
yield sd3.sdt.wait_for_nirvana(.5)
342
# clean up UDFs (removing from 1 should remove it from all)
343
debug(testprefix, 'Removing old folders.')
344
folders = yield sd1.sdt.get_folders()
346
vid = udf['volume_id']
347
debug(testprefix, 'Deleting UDF with id:', vid)
348
yield sd1.sdt.delete_folder(folder_id=vid)
350
# wait for SDs to finish
351
yield sd1.sdt.wait_for_nirvana(.5)
352
yield sd2.sdt.wait_for_nirvana(.5)
353
yield sd3.sdt.wait_for_nirvana(.5)
355
folders = yield sd1.sdt.get_folders()
357
folders = yield sd2.sdt.get_folders()
359
folders = yield sd3.sdt.get_folders()
362
debug(testprefix, 'Removing old data in home.')
363
for sd in sd1, sd2, sd3:
364
for something in os.listdir(sd.homedir):
365
if something not in ('Ubuntu One', '.local', '.config'):
366
fullpath = os.path.join(sd.homedir, something)
367
if os.path.isdir(fullpath):
368
shutil.rmtree(fullpath)
372
debug(testprefix, 'Removing old data in client main dir.')
373
for sd in sd1, sd2, sd3:
374
for something in os.listdir(sd.rootdir):
375
if something != 'Shared With Me':
376
fullpath = os.path.join(sd.rootdir, something)
377
if os.path.isdir(fullpath):
378
shutil.rmtree(fullpath)
382
# wait for SDs to finish these last changes
383
yield sd1.sdt.wait_for_nirvana(.5)
384
yield sd2.sdt.wait_for_nirvana(.5)
385
yield sd3.sdt.wait_for_nirvana(.5)
392
debug(testprefix, 'All done')
394
debug(prefix, "All tests passed ok!", previous_newline=True)
397
@defer.inlineCallbacks
398
def main(test_filter, repeat=False):
400
all_tests = get_all_tests(test_filter)
403
timestamp = time.strftime("%Y%m%d%M%H%S")
406
user1 = "integtest" + timestamp
407
user2 = "integotro" + timestamp
408
for u in (user1, user2):
410
debug(prefix, 'User created:', u)
412
debug(prefix, 'Content blobs created')
414
sd1 = SyncDaemon(user1, 1, timestamp, verbose=True)
415
debug(prefix, 'SyncDaemon 1 created.')
416
sd2 = SyncDaemon(user1, 2, timestamp, verbose=True)
417
debug(prefix, 'SyncDaemon 2 created.')
418
sd3 = SyncDaemon(user2, 3, timestamp, verbose=True)
419
debug(prefix, 'SyncDaemon 2 created.')
422
debug(prefix, 'SyncDaemon 1 started.')
424
debug(prefix, 'SyncDaemon 2 started.')
426
debug(prefix, 'SyncDaemon 3 started.')
433
debug(prefix, 'Executing tests, run', i)
434
yield execute_tests(all_tests, sd1, sd2, sd3)
436
yield execute_tests(all_tests, sd1, sd2, sd3)
437
debug(prefix, 'Tests executed.')
439
print '\n', '!' * 20, 'There was a problem. Failure below.', '!' * 20
441
print '!' * 20, 'There was a problem. Failure above.', '!' * 20
443
debug(prefix, 'End.')
445
debug(prefix, 'sd1 stopped.')
447
debug(prefix, 'sd2 stopped.')
449
debug(prefix, 'sd3 stopped.')
451
debug(prefix, 'reactor stopped.')
454
if __name__ == "__main__":
455
# check we're ready to go
456
if not deps_missing():
457
parser = OptionParser()
458
parser.add_option("-r", "--repeat", action="store_true", dest="repeat",
459
help="repeat the test(s) until failure")
460
(options, args) = parser.parse_args()
461
main(args, options.repeat)