~hadware/magicicada-server/trusty-support

« back to all changes in this revision

Viewing changes to lib/ubuntuone/storage/tests/integration/run_integtests.py

  • Committer: Facundo Batista
  • Date: 2015-08-05 13:10:02 UTC
  • Revision ID: facundo@taniquetil.com.ar-20150805131002-he7b7k704d8o7js6
First released version.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2008-2015 Canonical
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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/>.
 
15
#
 
16
# For further info, check  http://launchpad.net/filesync-server
 
17
 
 
18
"""Integration Tests for UDFs."""
 
19
 
 
20
import multiprocessing
 
21
import os
 
22
import random
 
23
import shutil
 
24
import signal
 
25
import subprocess
 
26
import sys
 
27
import time
 
28
 
 
29
from optparse import OptionParser
 
30
 
 
31
import dbus
 
32
import dbus.mainloop.glib  # this is black magic. DO NOT REMOVE!
 
33
 
 
34
from distutils.spawn import find_executable
 
35
 
 
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)
 
39
 
 
40
from twisted.internet import glib2reactor
 
41
glib2reactor.install()  # before any reactor import
 
42
 
 
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
 
47
 
 
48
from ubuntuone.storage.tests.integration.helpers import debug, retryable
 
49
 
 
50
# to make dbus work
 
51
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
52
 
 
53
 
 
54
# this should be done manually before:
 
55
DEP_STARTOAUTH = """
 
56
make start-oauth
 
57
"""
 
58
DEP_BUILDCLIENT = """
 
59
cd sourcecode/ubuntuone-client/
 
60
./autogen.sh --with-protocol=../ubuntuone-storage-protocol/
 
61
make
 
62
"""
 
63
 
 
64
 
 
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')
 
68
 
 
69
_dbus = find_executable("dbus-daemon")
 
70
if not _dbus:
 
71
    raise RuntimeError("dbus-daemon was not found.")
 
72
 
 
73
 
 
74
def start_DBus():
 
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,
 
79
                 "--print-address=1",
 
80
                 "--print-pid=2"]
 
81
    p = subprocess.Popen([_dbus] + dbus_args, bufsize=4096,
 
82
                         stdout=subprocess.PIPE,
 
83
                         stderr=subprocess.PIPE)
 
84
 
 
85
    dbus_address = "".join(p.stdout.readlines()).strip()
 
86
    dbus_pid = int("".join(p.stderr.readlines()).strip())
 
87
 
 
88
    if dbus_address == "":
 
89
        os.kill(dbus_pid, signal.SIGKILL)
 
90
        raise Exception("There was a problem launching dbus-daemon.")
 
91
 
 
92
    return dbus_pid, dbus_address
 
93
 
 
94
 
 
95
def deps_missing():
 
96
    """Check if we're ready to go.
 
97
 
 
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.
 
100
    """
 
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
 
104
        return True
 
105
 
 
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
 
110
        return True
 
111
 
 
112
 
 
113
def create_syncdaemon(username, procnum, homedir, pidfile):
 
114
    """Creates a SD with its DBus."""
 
115
    prefix = 'Create_syncdaemon:'
 
116
    # start dbus
 
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)
 
121
 
 
122
    # run the client
 
123
    env = dict(DBUS_SESSION_BUS_ADDRESS=dbus_address,
 
124
               PYTHONPATH=LIB_DIR,
 
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)))
 
130
        f.write('\n')
 
131
 
 
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)
 
137
 
 
138
 
 
139
class SyncDaemonToolProxy(object):
 
140
    """Call SDT methods, but retrying."""
 
141
 
 
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')
 
145
 
 
146
    def __getattr__(self, name):
 
147
        """Return SDT method wrapped in retryable."""
 
148
        original_method = getattr(self._sdt, name)
 
149
        return retryable(original_method)
 
150
 
 
151
 
 
152
class SyncDaemon(object):
 
153
    """SyncDaemon abstraction."""
 
154
 
 
155
    def __init__(self, username, procnum, timestamp, verbose=False):
 
156
        self._proc = None
 
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
 
167
        self.events = []
 
168
        self.sdt = None
 
169
 
 
170
        if os.path.exists(self.pidfile):
 
171
            os.remove(self.pidfile)
 
172
 
 
173
    def debug(self, msg, *args):
 
174
        """Print debug messages."""
 
175
        if self.verbose:
 
176
            debug(self.prefix, msg, *args)
 
177
 
 
178
    def parse_daemon_data(self):
 
179
        """Get dbus_pid, dbsu_address and homedir for process."""
 
180
        for i in range(10):
 
181
            if os.path.exists(self.pidfile):
 
182
                break
 
183
            self.debug("Process' pid file doesn't exist, sleeping 2 second",
 
184
                       self.pidfile)
 
185
            time.sleep(2)
 
186
        else:
 
187
            ValueError("ERROR: couldn't see the pidfile: %r" % (self.pidfile,))
 
188
 
 
189
        with open(self.pidfile) as f:
 
190
            content = f.read()
 
191
 
 
192
        return content.split()
 
193
 
 
194
    def record_event(self, event_dict):
 
195
        """Store events for further analysis."""
 
196
        self.events.append(event_dict)
 
197
 
 
198
    @defer.inlineCallbacks
 
199
    def start(self):
 
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,
 
204
                                             args=args)
 
205
        self.debug("Process created:", self._proc)
 
206
        self._proc.start()
 
207
        self.debug("Process started.")
 
208
 
 
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)
 
213
 
 
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)
 
218
 
 
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)
 
224
 
 
225
    def stop(self):
 
226
        """Kills the running syncdaemon."""
 
227
        self.debug("Stopping SyncDaemon", self.procnum)
 
228
        assert self.sdt._sdt is not None
 
229
        d = self.sdt.quit()
 
230
        d.addCallback(lambda _: os.kill(self._dbus_pid, signal.SIGKILL))
 
231
        d.addCallback(lambda _: os.remove(self.pidfile))
 
232
        return d
 
233
 
 
234
    def wait_for_event(self, event_name, **event_args):
 
235
        """Wait for a given event."""
 
236
        self.debug('Waiting for event', event_name)
 
237
 
 
238
        def filter(event):
 
239
            """Filter an event."""
 
240
            if event['event_name'] != event_name:
 
241
                return False
 
242
            for key, value in event_args:
 
243
                if event[key] != value:
 
244
                    return False
 
245
            return True
 
246
 
 
247
        return self.sdt.wait_for_signal('Event', filter)
 
248
 
 
249
 
 
250
def get_all_tests(test_filter):
 
251
    """Collect all tests to execute."""
 
252
    # convert the test filter to an usable dict
 
253
    testsok = {}
 
254
    for t in test_filter:
 
255
        if "." in t:
 
256
            fmod, ftest = t.split(".")
 
257
            testsok.setdefault(fmod, []).append(ftest)
 
258
        else:
 
259
            testsok[t] = None
 
260
 
 
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')]
 
264
 
 
265
    all_funcs = []
 
266
    for fname in test_files:
 
267
        modname = fname[:-3]
 
268
        mod = __import__(modname)
 
269
        funcnames = [x for x in dir(mod) if x.startswith('test_')]
 
270
 
 
271
        # check if in asked for tests
 
272
        funcnames_filtered = []
 
273
        if testsok == {}:
 
274
            # nothing required, all in
 
275
            funcnames_filtered = funcnames
 
276
        else:
 
277
            if modname in testsok:
 
278
                tests = testsok[modname]
 
279
                if tests is None:
 
280
                    # all the module is ok
 
281
                    funcnames_filtered = funcnames
 
282
                else:
 
283
                    funcnames_filtered = [n for n in funcnames if n in tests]
 
284
 
 
285
        funcs = [getattr(mod, x) for x in funcnames_filtered]
 
286
        all_funcs.extend(funcs)
 
287
 
 
288
    random.shuffle(all_funcs)
 
289
    return all_funcs
 
290
 
 
291
 
 
292
@defer.inlineCallbacks
 
293
def execute_tests(all_tests, sd1, sd2, sd3):
 
294
    """Execute the whole suite, please."""
 
295
    prefix = 'Execute tests:'
 
296
 
 
297
    debug(prefix, 'Starting')
 
298
    assert os.path.exists(sd1.rootdir), sd1.rootdir + ' must exist'
 
299
 
 
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"))
 
304
    yield dir_in_sd1
 
305
    debug(prefix, 'AQ_DIR_NEW_OK in sd1 done.')
 
306
    yield dir_in_sd2
 
307
    debug(prefix, 'FS_DIR_CREATE in sd2 done.')
 
308
 
 
309
    # calm down
 
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.')
 
313
 
 
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, "==="))
 
318
 
 
319
        skipit = getattr(test, 'skip', None)
 
320
        if skipit:
 
321
            debug(testprefix, 'Skipping test!', skipit, previous_newline=True)
 
322
            continue
 
323
 
 
324
        debug(testprefix, 'Starting test %d of %d' % (i + 1, len_tests),
 
325
              previous_newline=True)
 
326
 
 
327
        try:
 
328
            yield test(test_name, sd1, sd2, sd3, testprefix)
 
329
        except Exception, e:
 
330
            debug(testprefix, 'Crushing failure and despair, :( -- ', str(e))
 
331
            raise
 
332
        else:
 
333
            debug(testprefix, 'Test finished ok! :)')
 
334
        finally:
 
335
            debug(testprefix, 'Cleaning up.')
 
336
 
 
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)
 
341
 
 
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()
 
345
            for udf in folders:
 
346
                vid = udf['volume_id']
 
347
                debug(testprefix, 'Deleting UDF with id:', vid)
 
348
                yield sd1.sdt.delete_folder(folder_id=vid)
 
349
 
 
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)
 
354
 
 
355
            folders = yield sd1.sdt.get_folders()
 
356
            assert folders == []
 
357
            folders = yield sd2.sdt.get_folders()
 
358
            assert folders == []
 
359
            folders = yield sd3.sdt.get_folders()
 
360
            assert folders == []
 
361
 
 
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)
 
369
                        else:
 
370
                            os.remove(fullpath)
 
371
 
 
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)
 
379
                        else:
 
380
                            os.remove(fullpath)
 
381
 
 
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)
 
386
 
 
387
            # clean up events
 
388
            sd1.events = []
 
389
            sd2.events = []
 
390
            sd3.events = []
 
391
 
 
392
            debug(testprefix, 'All done')
 
393
 
 
394
    debug(prefix, "All tests passed ok!", previous_newline=True)
 
395
 
 
396
 
 
397
@defer.inlineCallbacks
 
398
def main(test_filter, repeat=False):
 
399
    """Main function."""
 
400
    all_tests = get_all_tests(test_filter)
 
401
 
 
402
    prefix = 'main:'
 
403
    timestamp = time.strftime("%Y%m%d%M%H%S")
 
404
 
 
405
    # create user
 
406
    user1 = "integtest" + timestamp
 
407
    user2 = "integotro" + timestamp
 
408
    for u in (user1, user2):
 
409
        create_test_user(u)
 
410
        debug(prefix, 'User created:', u)
 
411
 
 
412
    debug(prefix, 'Content blobs created')
 
413
 
 
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.')
 
420
 
 
421
    yield sd1.start()
 
422
    debug(prefix, 'SyncDaemon 1 started.')
 
423
    yield sd2.start()
 
424
    debug(prefix, 'SyncDaemon 2 started.')
 
425
    yield sd3.start()
 
426
    debug(prefix, 'SyncDaemon 3 started.')
 
427
 
 
428
    try:
 
429
        if repeat:
 
430
            i = 0
 
431
            while True:
 
432
                i += 1
 
433
                debug(prefix, 'Executing tests, run', i)
 
434
                yield execute_tests(all_tests, sd1, sd2, sd3)
 
435
        else:
 
436
            yield execute_tests(all_tests, sd1, sd2, sd3)
 
437
            debug(prefix, 'Tests executed.')
 
438
    except Exception, e:
 
439
        print '\n', '!' * 20, 'There was a problem. Failure below.', '!' * 20
 
440
        print e
 
441
        print '!' * 20, 'There was a problem. Failure above.', '!' * 20
 
442
 
 
443
    debug(prefix, 'End.')
 
444
    yield sd1.stop()
 
445
    debug(prefix, 'sd1 stopped.')
 
446
    yield sd2.stop()
 
447
    debug(prefix, 'sd2 stopped.')
 
448
    yield sd3.stop()
 
449
    debug(prefix, 'sd3 stopped.')
 
450
    reactor.stop()
 
451
    debug(prefix, 'reactor stopped.')
 
452
 
 
453
 
 
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)
 
462
        reactor.run()