2009-10-13 16:12:34 +02:00
|
|
|
#! /usr/bin/python
|
2009-10-14 17:59:03 +02:00
|
|
|
#
|
|
|
|
# Copyright (C) 2009 Intel Corporation
|
|
|
|
#
|
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) version 3.
|
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
# 02110-1301 USA
|
2009-10-13 16:12:34 +02:00
|
|
|
|
|
|
|
import random
|
|
|
|
import unittest
|
|
|
|
import subprocess
|
|
|
|
import time
|
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
import shutil
|
2009-10-14 17:59:03 +02:00
|
|
|
import copy
|
2009-11-18 22:39:43 +01:00
|
|
|
import heapq
|
2009-10-13 16:12:34 +02:00
|
|
|
|
|
|
|
import dbus
|
|
|
|
from dbus.mainloop.glib import DBusGMainLoop
|
2010-02-03 02:50:56 +01:00
|
|
|
import dbus.service
|
2009-10-13 16:12:34 +02:00
|
|
|
import gobject
|
2009-11-18 07:41:06 +01:00
|
|
|
import sys
|
|
|
|
|
2009-11-05 13:13:17 +01:00
|
|
|
# introduced in python-gobject 2.16, not available
|
|
|
|
# on all Linux distros => make it optional
|
|
|
|
try:
|
|
|
|
import glib
|
|
|
|
have_glib = True
|
|
|
|
except ImportError:
|
|
|
|
have_glib = False
|
2009-10-13 16:12:34 +02:00
|
|
|
|
|
|
|
DBusGMainLoop(set_as_default=True)
|
|
|
|
|
|
|
|
bus = dbus.SessionBus()
|
2009-10-14 17:59:03 +02:00
|
|
|
loop = gobject.MainLoop()
|
2009-10-13 16:12:34 +02:00
|
|
|
|
|
|
|
debugger = "" # "gdb"
|
|
|
|
server = ["syncevo-dbus-server"]
|
2009-10-14 14:53:14 +02:00
|
|
|
monitor = ["dbus-monitor"]
|
2009-10-13 16:12:34 +02:00
|
|
|
xdg_root = "test-dbus"
|
2010-01-21 09:15:19 +01:00
|
|
|
configName = "dbus_unittest"
|
2009-10-13 16:12:34 +02:00
|
|
|
|
2009-11-18 22:39:43 +01:00
|
|
|
def timeout(seconds):
|
|
|
|
"""Function decorator which sets a non-default timeout for a test.
|
|
|
|
The default timeout, enforced by DBusTest.runTest(), are 5 seconds.
|
|
|
|
Use like this:
|
|
|
|
@timeout(10)
|
|
|
|
def testMyTest:
|
|
|
|
...
|
|
|
|
"""
|
|
|
|
def __setTimeout(func):
|
|
|
|
func.timeout = seconds
|
|
|
|
return func
|
|
|
|
return __setTimeout
|
|
|
|
|
|
|
|
class Timeout:
|
|
|
|
"""Implements global time-delayed callbacks."""
|
|
|
|
alarms = []
|
|
|
|
next_alarm = None
|
|
|
|
previous_handler = None
|
|
|
|
debugTimeout = False
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def addTimeout(cls, delay_seconds, callback, use_glib=True):
|
|
|
|
"""Call function after a certain delay, specified in seconds.
|
|
|
|
If possible and use_glib=True, then it will only fire inside
|
|
|
|
glib event loop. Otherwise it uses signals. When signals are
|
|
|
|
used it is a bit uncertain what kind of Python code can
|
|
|
|
be executed. It was observed that trying to append to
|
|
|
|
DBusUtil.quit_events before calling loop.quit() caused
|
|
|
|
a KeyboardInterrupt"""
|
|
|
|
if have_glib and use_glib:
|
|
|
|
glib.timeout_add(delay_seconds, callback)
|
|
|
|
# TODO: implement removal of glib timeouts
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
now = time.time()
|
|
|
|
if cls.debugTimeout:
|
|
|
|
print "addTimeout", now, delay_seconds, callback, use_glib
|
|
|
|
timeout = (now + delay_seconds, callback)
|
|
|
|
heapq.heappush(cls.alarms, timeout)
|
|
|
|
cls.__check_alarms()
|
|
|
|
return timeout
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def removeTimeout(cls, timeout):
|
|
|
|
"""Remove a timeout returned by a previous addTimeout call.
|
|
|
|
None and timeouts which have already fired are acceptable."""
|
|
|
|
try:
|
|
|
|
cls.alarms.remove(timeout)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
heapq.heapify(cls.alarms)
|
|
|
|
cls.__check_alarms()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __handler(cls, signum, stack):
|
|
|
|
"""next_alarm has fired, check for expired timeouts and reinstall"""
|
|
|
|
if cls.debugTimeout:
|
|
|
|
print "fired", time.time()
|
|
|
|
cls.next_alarm = None
|
|
|
|
cls.__check_alarms()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __check_alarms(cls):
|
|
|
|
now = time.time()
|
|
|
|
while cls.alarms and cls.alarms[0][0] <= now:
|
|
|
|
timeout = heapq.heappop(cls.alarms)
|
|
|
|
if cls.debugTimeout:
|
|
|
|
print "invoking", timeout
|
|
|
|
timeout[1]()
|
|
|
|
|
|
|
|
if cls.alarms:
|
|
|
|
if not cls.next_alarm or \
|
|
|
|
cls.next_alarm > cls.alarms[0][0]:
|
|
|
|
if cls.previous_handler == None:
|
|
|
|
cls.previous_handler = signal.signal(signal.SIGALRM, cls.__handler)
|
|
|
|
cls.next_alarm = cls.alarms[0][0]
|
|
|
|
delay = int(cls.next_alarm - now + 0.5)
|
|
|
|
if not delay:
|
|
|
|
delay = 1
|
|
|
|
if cls.debugTimeout:
|
|
|
|
print "next alarm", cls.next_alarm, delay
|
|
|
|
signal.alarm(delay)
|
|
|
|
elif cls.next_alarm:
|
|
|
|
if cls.debugTimeout:
|
|
|
|
print "disarming alarm"
|
|
|
|
signal.alarm(0)
|
|
|
|
cls.next_alarm = None
|
|
|
|
|
|
|
|
# commented out because running it takes time
|
|
|
|
#class TestTimeout(unittest.TestCase):
|
|
|
|
class TimeoutTest:
|
|
|
|
"""unit test for Timeout mechanism"""
|
|
|
|
|
|
|
|
def testOneTimeout(self):
|
|
|
|
"""simple timeout of two seconds"""
|
|
|
|
self.called = False
|
|
|
|
start = time.time()
|
|
|
|
def callback():
|
|
|
|
self.called = True
|
|
|
|
Timeout.addTimeout(2, callback, use_glib=False)
|
|
|
|
time.sleep(10)
|
|
|
|
end = time.time()
|
|
|
|
self.failUnless(self.called)
|
|
|
|
self.failIf(end - start < 2)
|
|
|
|
self.failIf(end - start >= 3)
|
|
|
|
|
|
|
|
def testEmptyTimeout(self):
|
|
|
|
"""called immediately because of zero timeout"""
|
|
|
|
self.called = False
|
|
|
|
start = time.time()
|
|
|
|
def callback():
|
|
|
|
self.called = True
|
|
|
|
Timeout.addTimeout(0, callback, use_glib=False)
|
|
|
|
if not self.called:
|
|
|
|
time.sleep(10)
|
|
|
|
end = time.time()
|
|
|
|
self.failUnless(self.called)
|
|
|
|
self.failIf(end - start < 0)
|
|
|
|
self.failIf(end - start >= 1)
|
|
|
|
|
|
|
|
def testTwoTimeouts(self):
|
|
|
|
"""two timeouts after 2 and 5 seconds, installed in order"""
|
|
|
|
self.called = False
|
|
|
|
start = time.time()
|
|
|
|
def callback():
|
|
|
|
self.called = True
|
|
|
|
Timeout.addTimeout(2, callback, use_glib=False)
|
|
|
|
Timeout.addTimeout(5, callback, use_glib=False)
|
|
|
|
time.sleep(10)
|
|
|
|
end = time.time()
|
|
|
|
self.failUnless(self.called)
|
|
|
|
self.failIf(end - start < 2)
|
|
|
|
self.failIf(end - start >= 3)
|
|
|
|
time.sleep(10)
|
|
|
|
end = time.time()
|
|
|
|
self.failUnless(self.called)
|
|
|
|
self.failIf(end - start < 5)
|
|
|
|
self.failIf(end - start >= 6)
|
|
|
|
|
|
|
|
def testTwoReversedTimeouts(self):
|
|
|
|
"""two timeouts after 2 and 5 seconds, installed in reversed order"""
|
|
|
|
self.called = False
|
|
|
|
start = time.time()
|
|
|
|
def callback():
|
|
|
|
self.called = True
|
|
|
|
Timeout.addTimeout(5, callback, use_glib=False)
|
|
|
|
Timeout.addTimeout(2, callback, use_glib=False)
|
|
|
|
time.sleep(10)
|
|
|
|
end = time.time()
|
|
|
|
self.failUnless(self.called)
|
|
|
|
self.failIf(end - start < 2)
|
|
|
|
self.failIf(end - start >= 3)
|
|
|
|
time.sleep(10)
|
|
|
|
end = time.time()
|
|
|
|
self.failUnless(self.called)
|
|
|
|
self.failIf(end - start < 5)
|
|
|
|
self.failIf(end - start >= 6)
|
|
|
|
|
|
|
|
class DBusUtil(Timeout):
|
2009-10-14 17:59:03 +02:00
|
|
|
"""Contains the common run() method for all D-Bus test suites
|
|
|
|
and some utility functions."""
|
2009-10-13 16:12:34 +02:00
|
|
|
|
2009-11-18 22:49:48 +01:00
|
|
|
# Use class variables because that way it is ensured that there is
|
|
|
|
# only one set of them. Previously instance variables were used,
|
|
|
|
# which had the effect that D-Bus signal handlers from test A
|
|
|
|
# wrote into variables which weren't the ones used by test B.
|
|
|
|
# Unfortunately it is impossible to remove handlers when
|
|
|
|
# completing test A.
|
|
|
|
events = []
|
|
|
|
quit_events = []
|
|
|
|
reply = None
|
2009-11-05 18:04:41 +01:00
|
|
|
|
2010-01-19 11:06:45 +01:00
|
|
|
def runTest(self, result, own_xdg=True, serverArgs=[] ):
|
2009-10-13 16:12:34 +02:00
|
|
|
"""Starts the D-Bus server and dbus-monitor before the test
|
|
|
|
itself. After the test run, the output of these two commands
|
|
|
|
are added to the test's failure, if any. Otherwise the output
|
|
|
|
is ignored. A non-zero return code of the D-Bus server is
|
|
|
|
logged as separate failure.
|
|
|
|
|
|
|
|
The D-Bus server must print at least one line of output
|
|
|
|
before the test is allowed to start.
|
|
|
|
|
|
|
|
The commands are run with XDG_DATA_HOME, XDG_CONFIG_HOME,
|
|
|
|
XDG_CACHE_HOME pointing towards local dirs
|
|
|
|
test-dbus/[data|config|cache] which are removed before each
|
|
|
|
test."""
|
|
|
|
|
2009-11-18 22:49:48 +01:00
|
|
|
DBusUtil.events = []
|
|
|
|
DBusUtil.quit_events = []
|
|
|
|
DBusUtil.reply = None
|
|
|
|
|
2009-11-09 20:54:03 +01:00
|
|
|
kill = subprocess.Popen("sh -c 'killall -9 syncevo-dbus-server dbus-monitor >/dev/null 2>&1'", shell=True)
|
2009-11-05 17:59:50 +01:00
|
|
|
kill.communicate()
|
|
|
|
|
2009-11-18 07:41:06 +01:00
|
|
|
"""own_xdg is saved in self for we use this flag to check whether
|
|
|
|
copying reference directory tree."""
|
|
|
|
self.own_xdg = own_xdg
|
2009-10-14 17:59:03 +02:00
|
|
|
env = copy.deepcopy(os.environ)
|
|
|
|
if own_xdg:
|
|
|
|
shutil.rmtree(xdg_root, True)
|
|
|
|
env["XDG_DATA_HOME"] = xdg_root + "/data"
|
|
|
|
env["XDG_CONFIG_HOME"] = xdg_root + "/config"
|
|
|
|
env["XDG_CACHE_HOME"] = xdg_root + "/cache"
|
2009-10-13 16:12:34 +02:00
|
|
|
|
2009-11-05 17:59:50 +01:00
|
|
|
dbuslog = "dbus.log"
|
|
|
|
syncevolog = "syncevo.log"
|
2009-10-13 16:12:34 +02:00
|
|
|
pmonitor = subprocess.Popen(monitor,
|
2009-11-05 17:59:50 +01:00
|
|
|
stdout=open(dbuslog, "w"),
|
2009-10-13 16:12:34 +02:00
|
|
|
stderr=subprocess.STDOUT)
|
|
|
|
if debugger:
|
|
|
|
print "\n%s: %s\n" % (self.id(), self.shortDescription())
|
2009-10-14 17:59:03 +02:00
|
|
|
pserver = subprocess.Popen([debugger] + server,
|
|
|
|
env=env)
|
|
|
|
|
2009-10-13 16:12:34 +02:00
|
|
|
while True:
|
|
|
|
check = subprocess.Popen("ps x | grep %s | grep -w -v -e %s -e grep -e ps" % \
|
|
|
|
(server[0], debugger),
|
2009-10-29 11:02:31 +01:00
|
|
|
env=env,
|
2009-10-13 16:12:34 +02:00
|
|
|
shell=True,
|
|
|
|
stdout=subprocess.PIPE)
|
|
|
|
out, err = check.communicate()
|
|
|
|
if out:
|
|
|
|
# process exists, but might still be loading,
|
|
|
|
# so give it some more time
|
|
|
|
time.sleep(2)
|
|
|
|
break
|
|
|
|
else:
|
2010-01-19 11:06:45 +01:00
|
|
|
pserver = subprocess.Popen(server + serverArgs,
|
2009-10-14 17:59:03 +02:00
|
|
|
env=env,
|
2009-11-05 17:59:50 +01:00
|
|
|
stdout=open(syncevolog, "w"),
|
2009-10-13 16:12:34 +02:00
|
|
|
stderr=subprocess.STDOUT)
|
2009-11-05 17:59:50 +01:00
|
|
|
while os.path.getsize(syncevolog) == 0:
|
|
|
|
time.sleep(1)
|
2009-10-13 16:12:34 +02:00
|
|
|
|
|
|
|
numerrors = len(result.errors)
|
|
|
|
numfailures = len(result.failures)
|
|
|
|
if debugger:
|
|
|
|
print "\nrunning\n"
|
2009-11-18 22:39:43 +01:00
|
|
|
|
|
|
|
# Find out what test function we run and look into
|
|
|
|
# the function definition to see whether it comes
|
|
|
|
# with a non-default timeout, otherwise use a 5 second
|
|
|
|
# timeout.
|
|
|
|
test = eval(self.id().replace("__main__.", ""))
|
|
|
|
if "timeout" in dir(test):
|
|
|
|
timeout = test.timeout
|
|
|
|
else:
|
|
|
|
timeout = 5
|
2009-11-20 15:34:51 +01:00
|
|
|
timeout_handle = None
|
2009-11-18 22:39:43 +01:00
|
|
|
if timeout and not debugger:
|
|
|
|
def timedout():
|
|
|
|
error = "%s timed out after %d seconds" % (self.id(), timeout)
|
|
|
|
if Timeout.debugTimeout:
|
|
|
|
print error
|
|
|
|
raise Exception(error)
|
|
|
|
timeout_handle = self.addTimeout(timeout, timedout, use_glib=False)
|
|
|
|
try:
|
2009-11-18 22:49:48 +01:00
|
|
|
self.running = True
|
2009-11-18 22:39:43 +01:00
|
|
|
unittest.TestCase.run(self, result)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
# somehow this happens when timedout() above raises the exception
|
|
|
|
# while inside glib main loop
|
|
|
|
result.errors.append((self,
|
|
|
|
"interrupted by timeout or CTRL-C or Python signal handler problem"))
|
2009-11-18 22:49:48 +01:00
|
|
|
self.running = False
|
2009-11-18 22:39:43 +01:00
|
|
|
self.removeTimeout(timeout_handle)
|
2009-10-13 16:12:34 +02:00
|
|
|
if debugger:
|
|
|
|
print "\ndone, quit gdb now\n"
|
|
|
|
hasfailed = numerrors + numfailures != len(result.errors) + len(result.failures)
|
|
|
|
|
|
|
|
if not debugger:
|
2009-11-05 13:13:17 +01:00
|
|
|
os.kill(pserver.pid, signal.SIGTERM)
|
2009-11-05 17:59:50 +01:00
|
|
|
pserver.communicate()
|
|
|
|
serverout = open(syncevolog).read()
|
|
|
|
if pserver.returncode and pserver.returncode != -15:
|
|
|
|
hasfailed = True
|
2009-10-13 16:12:34 +02:00
|
|
|
if hasfailed:
|
|
|
|
# give D-Bus time to settle down
|
|
|
|
time.sleep(1)
|
2009-11-05 13:13:17 +01:00
|
|
|
os.kill(pmonitor.pid, signal.SIGTERM)
|
2009-11-05 17:59:50 +01:00
|
|
|
pmonitor.communicate()
|
|
|
|
monitorout = open(dbuslog).read()
|
2009-10-13 16:12:34 +02:00
|
|
|
report = "\n\nD-Bus traffic:\n%s\n\nserver output:\n%s\n" % \
|
|
|
|
(monitorout, serverout)
|
|
|
|
if pserver.returncode and pserver.returncode != -15:
|
|
|
|
# create a new failure specifically for the server
|
|
|
|
result.errors.append((self,
|
|
|
|
"server terminated with error code %d%s" % (pserver.returncode, report)))
|
|
|
|
elif numerrors != len(result.errors):
|
|
|
|
# append report to last error
|
|
|
|
result.errors[-1] = (result.errors[-1][0], result.errors[-1][1] + report)
|
|
|
|
elif numfailures != len(result.failures):
|
|
|
|
# same for failure
|
|
|
|
result.failures[-1] = (result.failures[-1][0], result.failures[-1][1] + report)
|
|
|
|
|
2009-10-14 17:59:03 +02:00
|
|
|
def setUpServer(self):
|
2009-10-13 16:12:34 +02:00
|
|
|
self.server = dbus.Interface(bus.get_object('org.syncevolution',
|
|
|
|
'/org/syncevolution/Server'),
|
|
|
|
'org.syncevolution.Server')
|
|
|
|
|
2010-08-25 10:37:58 +02:00
|
|
|
def createSession(self, config, wait, flags=[]):
|
2009-11-19 11:43:12 +01:00
|
|
|
"""Return sessionpath and session object for session using 'config'.
|
|
|
|
A signal handler calls loop.quit() when this session becomes ready.
|
|
|
|
If wait=True, then this call blocks until the session is ready.
|
|
|
|
"""
|
2010-08-25 10:37:58 +02:00
|
|
|
if flags:
|
|
|
|
sessionpath = self.server.StartSessionWithFlags(config, flags)
|
|
|
|
else:
|
|
|
|
sessionpath = self.server.StartSession(config)
|
2009-11-18 22:49:48 +01:00
|
|
|
|
|
|
|
def session_ready(object, ready):
|
2009-11-19 11:37:14 +01:00
|
|
|
if self.running and ready and object == sessionpath:
|
2009-11-18 22:49:48 +01:00
|
|
|
DBusUtil.quit_events.append("session " + object + " ready")
|
|
|
|
loop.quit()
|
|
|
|
|
2009-11-19 11:43:12 +01:00
|
|
|
signal = bus.add_signal_receiver(session_ready,
|
|
|
|
'SessionChanged',
|
|
|
|
'org.syncevolution.Server',
|
|
|
|
'org.syncevolution',
|
|
|
|
None,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
|
|
|
session = dbus.Interface(bus.get_object('org.syncevolution',
|
|
|
|
sessionpath),
|
|
|
|
'org.syncevolution.Session')
|
|
|
|
status, error, sources = session.GetStatus(utf8_strings=True)
|
|
|
|
if wait and status == "queuing":
|
|
|
|
# wait for signal
|
2009-10-14 17:59:03 +02:00
|
|
|
loop.run()
|
2009-11-19 11:43:12 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["session " + sessionpath + " ready"])
|
|
|
|
elif DBusUtil.quit_events:
|
|
|
|
# signal was processed inside D-Bus call?
|
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["session " + sessionpath + " ready"])
|
|
|
|
if wait:
|
|
|
|
# signal no longer needed, remove it because otherwise it
|
|
|
|
# might record unexpected "session ready" events
|
|
|
|
signal.remove()
|
|
|
|
DBusUtil.quit_events = []
|
|
|
|
return (sessionpath, session)
|
|
|
|
|
|
|
|
def setUpSession(self, config):
|
|
|
|
"""stores ready session in self.sessionpath and self.session"""
|
|
|
|
self.sessionpath, self.session = self.createSession(config, True)
|
2009-10-14 17:59:03 +02:00
|
|
|
|
2009-12-01 03:15:20 +01:00
|
|
|
def progressChanged(self, *args):
|
|
|
|
'''subclasses override this method to write specified callbacks for ProgressChanged signals
|
|
|
|
It is called by progress signal receivers in setUpListeners'''
|
|
|
|
pass
|
|
|
|
|
|
|
|
def statusChanged(self, *args):
|
|
|
|
'''subclasses override this method to write specified callbacks for StatusChanged signals
|
|
|
|
It is called by status signal receivers in setUpListeners'''
|
|
|
|
pass
|
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
def setUpListeners(self, sessionpath):
|
2009-11-18 22:49:48 +01:00
|
|
|
"""records progress and status changes in DBusUtil.events and
|
2009-11-05 18:04:41 +01:00
|
|
|
quits the main loop when the session is done"""
|
2009-11-18 22:49:48 +01:00
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
def progress(*args):
|
2009-11-18 22:49:48 +01:00
|
|
|
if self.running:
|
|
|
|
DBusUtil.events.append(("progress", args))
|
2009-12-01 03:15:20 +01:00
|
|
|
self.progressChanged(args)
|
2009-11-18 22:49:48 +01:00
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
def status(*args):
|
2009-11-18 22:49:48 +01:00
|
|
|
if self.running:
|
|
|
|
DBusUtil.events.append(("status", args))
|
2009-12-01 03:15:20 +01:00
|
|
|
self.statusChanged(args)
|
2009-11-18 22:49:48 +01:00
|
|
|
if args[0] == "done":
|
|
|
|
if sessionpath:
|
|
|
|
DBusUtil.quit_events.append("session " + sessionpath + " done")
|
|
|
|
else:
|
|
|
|
DBusUtil.quit_events.append("session done")
|
|
|
|
loop.quit()
|
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
bus.add_signal_receiver(progress,
|
|
|
|
'ProgressChanged',
|
|
|
|
'org.syncevolution.Session',
|
|
|
|
'org.syncevolution',
|
|
|
|
sessionpath,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
|
|
|
bus.add_signal_receiver(status,
|
|
|
|
'StatusChanged',
|
|
|
|
'org.syncevolution.Session',
|
|
|
|
'org.syncevolution',
|
|
|
|
sessionpath,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
|
|
|
|
|
|
|
def setUpConnectionListeners(self, conpath):
|
|
|
|
"""records connection signals (abort and reply), quits when
|
|
|
|
getting an abort"""
|
2009-11-18 22:49:48 +01:00
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
def abort():
|
2009-11-18 22:49:48 +01:00
|
|
|
if self.running:
|
|
|
|
DBusUtil.events.append(("abort",))
|
|
|
|
DBusUtil.quit_events.append("connection " + conpath + " aborted")
|
|
|
|
loop.quit()
|
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
def reply(*args):
|
2009-11-18 22:49:48 +01:00
|
|
|
if self.running:
|
|
|
|
DBusUtil.reply = args
|
|
|
|
if args[3]:
|
|
|
|
DBusUtil.quit_events.append("connection " + conpath + " got final reply")
|
|
|
|
else:
|
|
|
|
DBusUtil.quit_events.append("connection " + conpath + " got reply")
|
|
|
|
loop.quit()
|
2009-11-09 21:10:10 +01:00
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
bus.add_signal_receiver(abort,
|
|
|
|
'Abort',
|
|
|
|
'org.syncevolution.Connection',
|
|
|
|
'org.syncevolution',
|
|
|
|
conpath,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
|
|
|
bus.add_signal_receiver(reply,
|
|
|
|
'Reply',
|
|
|
|
'org.syncevolution.Connection',
|
|
|
|
'org.syncevolution',
|
|
|
|
conpath,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
2009-10-14 17:59:03 +02:00
|
|
|
|
2009-11-20 14:01:34 +01:00
|
|
|
def setupFiles(self, snapshot):
|
|
|
|
""" Copy reference directory trees from
|
|
|
|
test/test-dbus/<snapshot> to own xdg_root (=./test-dbus). To
|
|
|
|
be used only in tests which called runTest() with
|
|
|
|
own_xdg=True."""
|
|
|
|
self.failUnless(self.own_xdg)
|
2009-11-18 07:41:06 +01:00
|
|
|
|
|
|
|
# Get the absolute path of the current python file.
|
|
|
|
scriptpath = os.path.abspath(os.path.expanduser(os.path.expandvars(sys.argv[0])))
|
|
|
|
|
|
|
|
# reference directory 'test-dbus' is in the same directory as the current python file
|
2009-11-20 14:01:34 +01:00
|
|
|
sourcedir = os.path.join(os.path.dirname(scriptpath), 'test-dbus', snapshot)
|
2009-11-18 07:41:06 +01:00
|
|
|
|
|
|
|
""" Directories in test/test-dbus are copied to xdg_root, but
|
|
|
|
maybe with different names, mappings are:
|
2009-11-20 14:01:34 +01:00
|
|
|
test/test-dbus/<snapshot> ./test-dbus
|
2009-11-18 07:41:06 +01:00
|
|
|
sync4j .sync4j
|
|
|
|
data data
|
|
|
|
config config
|
|
|
|
cache cache """
|
|
|
|
pairs = { 'sync4j' : '.sync4j', 'config' : 'config', 'cache' : 'cache', 'data' : 'data'}
|
|
|
|
for src, dest in pairs.items():
|
|
|
|
destpath = os.path.join(xdg_root, dest)
|
|
|
|
# make sure the dest directory does not exist, which is required by shutil.copytree
|
|
|
|
shutil.rmtree(destpath, True)
|
|
|
|
sourcepath = os.path.join(sourcedir, src)
|
|
|
|
# if source exists and could be accessed, then copy them
|
|
|
|
if os.access(sourcepath, os.F_OK):
|
|
|
|
shutil.copytree(sourcepath, destpath)
|
|
|
|
|
2010-02-02 05:52:40 +01:00
|
|
|
def getDatabaseName(self, configName):
|
|
|
|
# get database names with the environment variable
|
|
|
|
prefix = os.getenv("CLIENT_TEST_EVOLUTION_PREFIX")
|
|
|
|
source = configName + '_1'
|
|
|
|
if prefix == None:
|
|
|
|
prefix = 'SyncEvolution_Test_'
|
|
|
|
return prefix + source;
|
|
|
|
|
|
|
|
def getEvolutionSources(self, config):
|
|
|
|
# get 'evolutionsource' for each source
|
|
|
|
updateProps = { }
|
|
|
|
for key, value in config.items():
|
|
|
|
if key != "":
|
|
|
|
tmpdict = { }
|
|
|
|
[source, sep, name] = key.partition('/')
|
|
|
|
if sep == '/':
|
|
|
|
tmpdict["evolutionsource"] = self.getDatabaseName(name)
|
|
|
|
updateProps[key] = tmpdict
|
|
|
|
return updateProps
|
|
|
|
|
2009-10-14 17:59:03 +02:00
|
|
|
class TestDBusServer(unittest.TestCase, DBusUtil):
|
|
|
|
"""Tests for the read-only Server API."""
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result)
|
|
|
|
|
2010-08-24 21:39:00 +02:00
|
|
|
def testCapabilities(self):
|
|
|
|
"""check the Server.GetCapabilities() call"""
|
2010-08-25 10:37:58 +02:00
|
|
|
capabilities = self.server.GetCapabilities()
|
|
|
|
capabilities.sort()
|
2010-08-25 14:54:11 +02:00
|
|
|
self.failUnlessEqual(capabilities, ['Notifications', 'SessionFlags', 'Version'])
|
2010-08-24 21:39:00 +02:00
|
|
|
|
|
|
|
def testVersions(self):
|
|
|
|
"""check the Server.GetVersions() call"""
|
|
|
|
versions = self.server.GetVersions()
|
|
|
|
self.failIfEqual(versions["version"], "")
|
|
|
|
self.failIfEqual(versions["system"], None)
|
|
|
|
self.failIfEqual(versions["backends"], None)
|
|
|
|
|
2009-10-13 16:12:34 +02:00
|
|
|
def testGetConfigsEmpty(self):
|
|
|
|
"""GetConfigs() with no configurations available"""
|
2009-10-14 17:59:03 +02:00
|
|
|
configs = self.server.GetConfigs(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(configs, [])
|
2009-10-13 16:12:34 +02:00
|
|
|
|
|
|
|
def testGetConfigsTemplates(self):
|
|
|
|
"""read templates"""
|
2009-10-14 17:59:03 +02:00
|
|
|
configs = self.server.GetConfigs(True, utf8_strings=True)
|
2009-10-29 11:03:39 +01:00
|
|
|
configs.sort()
|
|
|
|
self.failUnlessEqual(configs, ["Funambol",
|
|
|
|
"Google",
|
2010-01-21 09:15:19 +01:00
|
|
|
"Goosync",
|
2009-10-29 11:03:39 +01:00
|
|
|
"Memotoo",
|
|
|
|
"Mobical",
|
2010-01-21 09:15:19 +01:00
|
|
|
"Oracle",
|
2010-03-30 08:16:57 +02:00
|
|
|
"Ovi",
|
2009-10-29 11:03:39 +01:00
|
|
|
"ScheduleWorld",
|
2010-01-21 09:15:19 +01:00
|
|
|
"SyncEvolution",
|
2010-08-24 21:35:31 +02:00
|
|
|
"Synthesis"])
|
2009-10-13 16:12:34 +02:00
|
|
|
|
|
|
|
def testGetConfigScheduleWorld(self):
|
|
|
|
"""read ScheduleWorld template"""
|
2009-10-14 17:59:03 +02:00
|
|
|
config1 = self.server.GetConfig("scheduleworld", True, utf8_strings=True)
|
|
|
|
config2 = self.server.GetConfig("ScheduleWorld", True, utf8_strings=True)
|
2009-10-13 16:12:34 +02:00
|
|
|
self.failIfEqual(config1[""]["deviceId"], config2[""]["deviceId"])
|
|
|
|
config1[""]["deviceId"] = "foo"
|
|
|
|
config2[""]["deviceId"] = "foo"
|
|
|
|
self.failUnlessEqual(config1, config2)
|
|
|
|
|
2009-11-10 14:20:01 +01:00
|
|
|
def testInvalidConfig(self):
|
|
|
|
"""check that the right error is reported for invalid config name"""
|
|
|
|
try:
|
|
|
|
config1 = self.server.GetConfig("no-such-config", False, utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"org.syncevolution.NoSuchConfig: No configuration 'no-such-config' found")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-10 14:20:01 +01:00
|
|
|
|
2010-01-19 11:06:45 +01:00
|
|
|
class TestDBusServerTerm(unittest.TestCase, DBusUtil):
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result, True, ["-d", "10"])
|
|
|
|
|
|
|
|
@timeout(100)
|
|
|
|
def testNoTerm(self):
|
|
|
|
"""Test the dbus server stays alive within the duration"""
|
|
|
|
"""The server should stay alive because we have dbus call within
|
|
|
|
the duration. The loop is to make sure the total time is longer
|
|
|
|
than duration and the dbus server still stays alive for dbus calls."""
|
|
|
|
for i in range(0, 4):
|
|
|
|
time.sleep(4)
|
|
|
|
try:
|
|
|
|
self.server.GetConfigs(True, utf8_strings=True)
|
|
|
|
except dbus.DBusException:
|
|
|
|
self.fail("dbus server should work correctly")
|
|
|
|
|
|
|
|
@timeout(100)
|
|
|
|
def testTerm(self):
|
|
|
|
"""Test the dbus server terminates automatically after a duration"""
|
|
|
|
#sleep a duration and wait for syncevo-dbus-server termination
|
|
|
|
time.sleep(16)
|
|
|
|
try:
|
|
|
|
self.server.GetConfigs(True, utf8_strings=True)
|
|
|
|
except dbus.DBusException:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
|
|
|
|
|
|
|
@timeout(100)
|
|
|
|
def testTermConnection(self):
|
|
|
|
"""Test the dbus server doesn't terminate if it has connections"""
|
|
|
|
conpath = self.server.Connect({'description': 'test-dbus.py',
|
|
|
|
'transport': 'dummy'},
|
|
|
|
False,
|
|
|
|
"")
|
|
|
|
time.sleep(16)
|
|
|
|
try:
|
|
|
|
self.server.GetConfigs(True, utf8_strings=True)
|
|
|
|
except dbus.DBusException:
|
|
|
|
self.fail("dbus server should not terminate")
|
|
|
|
|
|
|
|
connection = dbus.Interface(bus.get_object('org.syncevolution',
|
|
|
|
conpath),
|
|
|
|
'org.syncevolution.Connection')
|
|
|
|
connection.Close(False, "good bye", utf8_strings=True)
|
|
|
|
time.sleep(16)
|
|
|
|
try:
|
|
|
|
self.server.GetConfigs(True, utf8_strings=True)
|
|
|
|
except dbus.DBusException:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
|
|
|
|
|
|
|
@timeout(100)
|
|
|
|
def testTermAttachedClients(self):
|
|
|
|
"""Test the dbus server doesn't terminate if it has attached clients"""
|
|
|
|
"""Also it tries to test the dbus server's behavior when a client
|
|
|
|
attaches the server many times"""
|
|
|
|
self.server.Attach()
|
|
|
|
self.server.Attach()
|
|
|
|
time.sleep(16)
|
|
|
|
try:
|
|
|
|
self.server.GetConfigs(True, utf8_strings=True)
|
|
|
|
except dbus.DBusException:
|
|
|
|
self.fail("dbus server should not terminate")
|
|
|
|
self.server.Detach()
|
|
|
|
time.sleep(16)
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.server.GetConfigs(True, utf8_strings=True)
|
|
|
|
except dbus.DBusException:
|
|
|
|
self.fail("dbus server should not terminate")
|
|
|
|
|
|
|
|
self.server.Detach()
|
|
|
|
time.sleep(16)
|
|
|
|
try:
|
|
|
|
self.server.GetConfigs(True, utf8_strings=True)
|
|
|
|
except dbus.DBusException:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
|
|
|
|
2010-02-03 02:50:56 +01:00
|
|
|
class Connman (dbus.service.Object):
|
|
|
|
count = 0
|
|
|
|
@dbus.service.method(dbus_interface='org.moblin.connman.Manager', in_signature='', out_signature='a{sv}')
|
|
|
|
def GetProperties(self):
|
|
|
|
self.count = self.count+1
|
|
|
|
if (self.count == 1):
|
|
|
|
loop.quit()
|
|
|
|
return {"ConnectedTechnologies":["ethernet", "some other stuff"],
|
2010-04-01 04:15:20 +02:00
|
|
|
"AvailableTechnologies": ["bluetooth"]}
|
2010-02-03 02:50:56 +01:00
|
|
|
elif (self.count == 2):
|
|
|
|
""" unplug the ethernet cable """
|
|
|
|
loop.quit()
|
|
|
|
return {"ConnectedTechnologies":["some other stuff"],
|
2010-04-01 04:15:20 +02:00
|
|
|
"AvailableTechnologies": ["bluetooth"]}
|
2010-02-03 02:50:56 +01:00
|
|
|
elif (self.count == 3):
|
|
|
|
""" replug the ethernet cable """
|
|
|
|
loop.quit()
|
|
|
|
return {"ConnectedTechnologies":["ethernet", "some other stuff"],
|
2010-04-01 04:15:20 +02:00
|
|
|
"AvailableTechnologies": ["bluetooth"]}
|
2010-02-03 02:50:56 +01:00
|
|
|
elif (self.count == 4):
|
|
|
|
""" nothing presence """
|
|
|
|
loop.quit()
|
|
|
|
return {"ConnectedTechnologies":[""],
|
2010-04-01 04:15:20 +02:00
|
|
|
"AvailableTechnologies": [""]}
|
2010-02-03 02:50:56 +01:00
|
|
|
elif (self.count == 5):
|
|
|
|
""" come back the same time """
|
|
|
|
loop.quit()
|
|
|
|
return {"ConnectedTechnologies":["ethernet", "some other stuff"],
|
2010-04-01 04:15:20 +02:00
|
|
|
"AvailableTechnologies": ["bluetooth"]}
|
|
|
|
|
|
|
|
@dbus.service.signal(dbus_interface='org.moblin.connman.Manager', signature='sv')
|
|
|
|
def PropertyChanged(self, key, value):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def emitSignal(self):
|
|
|
|
self.count = self.count+1
|
|
|
|
if (self.count == 2):
|
|
|
|
""" unplug the ethernet cable """
|
|
|
|
self.PropertyChanged("ConnectedTechnologies",["some other stuff"])
|
|
|
|
return
|
|
|
|
elif (self.count == 3):
|
|
|
|
""" replug the ethernet cable """
|
|
|
|
self.PropertyChanged("ConnectedTechnologies", ["ethernet", "some other stuff"])
|
|
|
|
return
|
|
|
|
elif (self.count == 4):
|
|
|
|
""" nothing presence """
|
|
|
|
self.PropertyChanged("ConnectedTechnologies", [""])
|
|
|
|
self.PropertyChanged("AvailableTechnologies", [""])
|
|
|
|
return
|
|
|
|
elif (self.count == 5):
|
|
|
|
""" come back the same time """
|
|
|
|
self.PropertyChanged("ConnectedTechnologies", ["ethernet", "some other stuff"])
|
|
|
|
self.PropertyChanged("AvailableTechnologies", ["bluetooth"])
|
|
|
|
return
|
|
|
|
|
2010-02-03 02:50:56 +01:00
|
|
|
def reset(self):
|
|
|
|
self.count = 0
|
|
|
|
|
|
|
|
class TestDBusServerPresence(unittest.TestCase, DBusUtil):
|
|
|
|
"""Tests Presence signal and checkPresence API"""
|
|
|
|
name = dbus.service.BusName ("org.moblin.connman", bus);
|
|
|
|
conn = Connman (bus, "/")
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
|
|
|
|
|
|
|
@timeout(100)
|
|
|
|
def testPresenceSignal(self):
|
|
|
|
self.conn.reset()
|
|
|
|
self.setUpSession("foo")
|
|
|
|
self.session.SetConfig(False, False, {"" : {"syncURL":
|
|
|
|
"http://http-only-1"}})
|
|
|
|
self.session.Detach()
|
|
|
|
def cb_http_presence(server, status, transport):
|
|
|
|
self.failUnlessEqual (status, "")
|
|
|
|
self.failUnlessEqual (server, "foo")
|
|
|
|
self.failUnlessEqual (transport, "http://http-only-1")
|
2010-04-01 04:15:20 +02:00
|
|
|
loop.quit()
|
2010-02-03 02:50:56 +01:00
|
|
|
|
|
|
|
match = bus.add_signal_receiver(cb_http_presence,
|
|
|
|
'Presence',
|
|
|
|
'org.syncevolution.Server',
|
|
|
|
'org.syncevolution',
|
|
|
|
None,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
|
|
|
loop.run()
|
|
|
|
time.sleep(1)
|
|
|
|
self.setUpSession("foo")
|
2010-04-01 04:15:20 +02:00
|
|
|
self.session.SetConfig(True, False, {"" : {"syncURL":
|
2010-02-03 02:50:56 +01:00
|
|
|
"obex-bt://temp-bluetooth-peer-changed-from-http"}})
|
|
|
|
def cb_bt_presence(server, status, transport):
|
|
|
|
self.failUnlessEqual (status, "")
|
|
|
|
self.failUnlessEqual (server, "foo")
|
|
|
|
self.failUnlessEqual (transport,
|
|
|
|
"obex-bt://temp-bluetooth-peer-changed-from-http")
|
2010-04-01 04:15:20 +02:00
|
|
|
loop.quit()
|
2010-02-03 02:50:56 +01:00
|
|
|
match.remove()
|
|
|
|
match = bus.add_signal_receiver(cb_bt_presence,
|
|
|
|
'Presence',
|
|
|
|
'org.syncevolution.Server',
|
|
|
|
'org.syncevolution',
|
|
|
|
None,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
2010-04-01 04:15:20 +02:00
|
|
|
self.conn.emitSignal()
|
2010-02-03 02:50:56 +01:00
|
|
|
loop.run()
|
|
|
|
time.sleep(1)
|
|
|
|
self.session.Detach()
|
|
|
|
self.setUpSession("bar")
|
|
|
|
self.session.SetConfig(False, False, {"" : {"syncURL":
|
|
|
|
"http://http-client-2"}})
|
|
|
|
self.session.Detach()
|
|
|
|
self.foo = "random string"
|
|
|
|
self.bar = "random string"
|
2010-04-01 04:15:20 +02:00
|
|
|
match.remove()
|
|
|
|
self.conn.emitSignal()
|
|
|
|
self.conn.emitSignal()
|
2010-02-03 02:50:56 +01:00
|
|
|
def cb_bt_http_presence(server, status, transport):
|
|
|
|
if (server == "foo"):
|
|
|
|
self.foo = status
|
|
|
|
elif (server == "bar"):
|
|
|
|
self.bar = status
|
|
|
|
else:
|
|
|
|
self.fail("wrong server config")
|
2010-04-01 04:15:20 +02:00
|
|
|
loop.quit()
|
2010-02-03 02:50:56 +01:00
|
|
|
|
|
|
|
match = bus.add_signal_receiver(cb_bt_http_presence,
|
|
|
|
'Presence',
|
|
|
|
'org.syncevolution.Server',
|
|
|
|
'org.syncevolution',
|
|
|
|
None,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
2010-04-01 04:15:20 +02:00
|
|
|
#count=5, 2 signals recevied
|
|
|
|
self.conn.emitSignal()
|
2010-02-03 02:50:56 +01:00
|
|
|
loop.run()
|
|
|
|
loop.run()
|
|
|
|
time.sleep(1)
|
|
|
|
self.failUnlessEqual (self.foo, "")
|
|
|
|
self.failUnlessEqual (self.bar, "")
|
|
|
|
match.remove()
|
|
|
|
|
|
|
|
@timeout(100)
|
|
|
|
def testServerCheckPresence(self):
|
|
|
|
self.conn.reset()
|
|
|
|
self.setUpSession("foo")
|
|
|
|
self.session.SetConfig(False, False, {"" : {"syncURL":
|
|
|
|
"http://http-client"}})
|
|
|
|
self.session.Detach()
|
|
|
|
self.setUpSession("bar")
|
|
|
|
self.session.SetConfig(False, False, {"" : {"syncURL":
|
|
|
|
"obex-bt://bt-client"}})
|
|
|
|
self.session.Detach()
|
|
|
|
self.setUpSession("foobar")
|
|
|
|
self.session.SetConfig(False, False, {"" : {"syncURL":
|
|
|
|
"obex-bt://bt-client-mixed http://http-client-mixed"}})
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
#let dbus server get the first presence
|
|
|
|
loop.run()
|
|
|
|
time.sleep(1)
|
|
|
|
(status, transports) = self.server.CheckPresence ("foo")
|
|
|
|
self.failUnlessEqual (status, "")
|
|
|
|
self.failUnlessEqual (transports, ["http://http-client"])
|
|
|
|
(status, transports) = self.server.CheckPresence ("bar")
|
|
|
|
self.failUnlessEqual (status, "")
|
|
|
|
self.failUnlessEqual (transports, ["obex-bt://bt-client"])
|
|
|
|
(status, transports) = self.server.CheckPresence ("foobar")
|
|
|
|
self.failUnlessEqual (status, "")
|
|
|
|
self.failUnlessEqual (transports, ["obex-bt://bt-client-mixed",
|
|
|
|
"http://http-client-mixed"])
|
|
|
|
|
|
|
|
#count = 2
|
2010-04-01 04:15:20 +02:00
|
|
|
self.conn.emitSignal()
|
2010-02-03 02:50:56 +01:00
|
|
|
time.sleep(1)
|
|
|
|
(status, transports) = self.server.CheckPresence ("foo")
|
|
|
|
self.failUnlessEqual (status, "no transport")
|
|
|
|
(status, transports) = self.server.CheckPresence ("bar")
|
|
|
|
self.failUnlessEqual (status, "")
|
|
|
|
self.failUnlessEqual (transports, ["obex-bt://bt-client"])
|
|
|
|
(status, transports) = self.server.CheckPresence ("foobar")
|
|
|
|
self.failUnlessEqual (status, "")
|
|
|
|
self.failUnlessEqual (transports, ["obex-bt://bt-client-mixed"])
|
|
|
|
|
|
|
|
@timeout(100)
|
|
|
|
def testSessionCheckPresence(self):
|
|
|
|
self.conn.reset()
|
|
|
|
self.setUpSession("foobar")
|
|
|
|
self.session.SetConfig(False, False, {"" : {"syncURL":
|
|
|
|
"obex-bt://bt-client-mixed http://http-client-mixed"}})
|
|
|
|
loop.run()
|
|
|
|
time.sleep(1)
|
|
|
|
status = self.session.checkPresence()
|
|
|
|
self.failUnlessEqual (status, "")
|
2010-04-01 04:15:20 +02:00
|
|
|
self.conn.emitSignal()
|
|
|
|
self.conn.emitSignal()
|
|
|
|
self.conn.emitSignal()
|
2010-02-03 02:50:56 +01:00
|
|
|
#count = 4
|
|
|
|
time.sleep(1)
|
|
|
|
status = self.session.checkPresence()
|
|
|
|
self.failUnlessEqual (status, "no transport")
|
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
os.environ["DBUS_TEST_CONNMAN"] = "session"
|
|
|
|
self.runTest(result, True)
|
|
|
|
|
2009-10-14 17:59:03 +02:00
|
|
|
class TestDBusSession(unittest.TestCase, DBusUtil):
|
|
|
|
"""Tests that work with an active session."""
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
|
|
|
self.setUpSession("")
|
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result)
|
|
|
|
|
|
|
|
def testCreateSession(self):
|
|
|
|
"""ask for session"""
|
2010-08-25 10:37:58 +02:00
|
|
|
self.failUnlessEqual(self.session.GetFlags(), [])
|
|
|
|
|
|
|
|
def testCreateSessionWithFlags(self):
|
|
|
|
"""ask for session with some specific flags"""
|
|
|
|
self.session.Detach()
|
|
|
|
self.sessionpath, self.session = self.createSession("", True, ["foo", "bar"])
|
|
|
|
self.failUnlessEqual(self.session.GetFlags(), ["foo", "bar"])
|
2009-10-14 17:59:03 +02:00
|
|
|
|
2009-11-18 22:39:43 +01:00
|
|
|
@timeout(20)
|
2009-10-14 17:59:03 +02:00
|
|
|
def testSecondSession(self):
|
|
|
|
"""a second session should not run unless the first one stops"""
|
2009-11-20 09:36:26 +01:00
|
|
|
sessions = self.server.GetSessions()
|
|
|
|
self.failUnlessEqual(sessions, [self.sessionpath])
|
2009-10-14 17:59:03 +02:00
|
|
|
sessionpath = self.server.StartSession("")
|
2009-11-20 09:36:26 +01:00
|
|
|
sessions = self.server.GetSessions()
|
|
|
|
self.failUnlessEqual(sessions, [self.sessionpath, sessionpath])
|
2009-11-18 22:49:48 +01:00
|
|
|
|
|
|
|
def session_ready(object, ready):
|
|
|
|
if self.running:
|
|
|
|
DBusUtil.quit_events.append("session " + object + (ready and " ready" or " done"))
|
|
|
|
loop.quit()
|
|
|
|
|
|
|
|
bus.add_signal_receiver(session_ready,
|
2009-10-14 17:59:03 +02:00
|
|
|
'SessionChanged',
|
|
|
|
'org.syncevolution.Server',
|
|
|
|
'org.syncevolution',
|
2009-11-19 11:37:14 +01:00
|
|
|
None,
|
2009-10-14 17:59:03 +02:00
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
2009-11-19 13:44:22 +01:00
|
|
|
|
|
|
|
def status(*args):
|
|
|
|
if self.running:
|
|
|
|
DBusUtil.events.append(("status", args))
|
|
|
|
if args[0] == "idle":
|
|
|
|
DBusUtil.quit_events.append("session " + sessionpath + " idle")
|
|
|
|
loop.quit()
|
|
|
|
|
|
|
|
bus.add_signal_receiver(status,
|
|
|
|
'StatusChanged',
|
|
|
|
'org.syncevolution.Session',
|
|
|
|
'org.syncevolution',
|
|
|
|
sessionpath,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
|
|
|
|
2009-10-14 17:59:03 +02:00
|
|
|
session = dbus.Interface(bus.get_object('org.syncevolution',
|
|
|
|
sessionpath),
|
|
|
|
'org.syncevolution.Session')
|
|
|
|
status, error, sources = session.GetStatus(utf8_strings=True)
|
|
|
|
self.failUnlessEqual(status, "queueing")
|
|
|
|
# use hash so that we can write into it in callback()
|
|
|
|
callback_called = {}
|
|
|
|
def callback():
|
2009-11-18 22:49:48 +01:00
|
|
|
callback_called[1] = "callback()"
|
2009-10-14 17:59:03 +02:00
|
|
|
self.session.Detach()
|
2010-05-27 17:31:35 +02:00
|
|
|
try:
|
|
|
|
t1 = self.addTimeout(2, callback)
|
|
|
|
# session 1 done
|
|
|
|
loop.run()
|
|
|
|
self.failUnless(callback_called)
|
|
|
|
# session 2 ready and idle
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
|
|
|
expected = ["session " + self.sessionpath + " done",
|
|
|
|
"session " + sessionpath + " idle",
|
|
|
|
"session " + sessionpath + " ready"]
|
|
|
|
expected.sort()
|
|
|
|
DBusUtil.quit_events.sort()
|
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, expected)
|
|
|
|
status, error, sources = session.GetStatus(utf8_strings=True)
|
|
|
|
self.failUnlessEqual(status, "idle")
|
|
|
|
finally:
|
|
|
|
self.removeTimeout(t1)
|
2009-10-14 17:59:03 +02:00
|
|
|
|
2009-11-16 03:31:31 +01:00
|
|
|
class TestSessionAPIsEmptyName(unittest.TestCase, DBusUtil):
|
|
|
|
"""Test session APIs that work with an empty server name. Thus, all of session APIs which
|
|
|
|
need this kind of checking are put in this class. """
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
|
|
|
self.setUpSession("")
|
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result)
|
|
|
|
|
|
|
|
def testGetConfigEmptyName(self):
|
2009-11-20 15:29:25 +01:00
|
|
|
"""reading empty default config"""
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
2009-11-16 03:31:31 +01:00
|
|
|
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
def testGetTemplateEmptyName(self):
|
|
|
|
"""trigger error by getting template for empty server name"""
|
2009-11-16 03:31:31 +01:00
|
|
|
try:
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
config = self.session.GetConfig(True, utf8_strings=True)
|
2009-11-16 03:31:31 +01:00
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"org.syncevolution.NoSuchConfig: No template '' found")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-11 03:01:13 +01:00
|
|
|
|
2009-11-13 07:57:01 +01:00
|
|
|
def testCheckSourceEmptyName(self):
|
|
|
|
"""Test the error is reported when the server name is empty for CheckSource"""
|
|
|
|
try:
|
|
|
|
self.session.CheckSource("", utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"org.syncevolution.NoSuchSource: '' has no '' source")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-13 07:57:01 +01:00
|
|
|
|
2009-11-13 08:05:00 +01:00
|
|
|
def testGetDatabasesEmptyName(self):
|
|
|
|
"""Test the error is reported when the server name is empty for GetDatabases"""
|
|
|
|
try:
|
|
|
|
self.session.GetDatabases("", utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
2009-11-20 15:29:25 +01:00
|
|
|
"org.syncevolution.NoSuchSource: '' has no '' source")
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-13 08:05:00 +01:00
|
|
|
|
2009-11-14 09:45:51 +01:00
|
|
|
def testGetReportsEmptyName(self):
|
2009-12-03 10:37:00 +01:00
|
|
|
"""Test reports from all peers are returned in order when the peer name is empty for GetReports"""
|
|
|
|
self.setupFiles('reports')
|
|
|
|
reports = self.session.GetReports(0, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(len(reports), 7)
|
|
|
|
refPeers = ["dummy-test", "dummy", "dummy-test", "dummy-test",
|
|
|
|
"dummy-test", "dummy_test", "dummy-test"]
|
|
|
|
for i in range(0, len(refPeers)):
|
|
|
|
self.failUnlessEqual(reports[i]["peer"], refPeers[i])
|
|
|
|
|
2010-01-21 07:06:07 +01:00
|
|
|
def testGetReportsContext(self):
|
|
|
|
"""Test reports from a context are returned when the peer name is empty for GetReports"""
|
|
|
|
self.setupFiles('reports')
|
|
|
|
self.session.Detach()
|
|
|
|
self.setUpSession("@context")
|
|
|
|
reports = self.session.GetReports(0, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(len(reports), 1)
|
|
|
|
self.failUnless(reports[0]["dir"].endswith("dummy_+test@context-2010-01-20-10-10"))
|
|
|
|
|
2009-11-14 09:45:51 +01:00
|
|
|
|
2009-11-13 07:57:01 +01:00
|
|
|
class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
|
|
|
|
"""Tests that work for GetConfig/SetConfig/CheckSource/GetDatabases/GetReports in Session.
|
|
|
|
This class is only working in a dummy config. Thus it can't do sync correctly. The purpose
|
|
|
|
is to test some cleanup cases and expected errors. Also, some unit tests for some APIs
|
|
|
|
depend on a clean configuration so they are included here. For those unit tests depending
|
|
|
|
on sync, another class is used """
|
2009-11-11 03:01:13 +01:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
2009-11-18 07:28:12 +01:00
|
|
|
# use 'dummy-test' as the server name
|
|
|
|
self.setUpSession("dummy-test")
|
2009-11-11 03:01:13 +01:00
|
|
|
# default config
|
|
|
|
self.config = {
|
2010-01-21 09:15:19 +01:00
|
|
|
"" : { "syncURL" : "http://impossible-syncurl-just-for-testing-to-avoid-conflict",
|
2009-11-11 03:01:13 +01:00
|
|
|
"username" : "unknown",
|
2009-12-24 08:07:48 +01:00
|
|
|
"password" : "-",
|
|
|
|
"deviceId" : "foo",
|
2010-01-21 09:15:19 +01:00
|
|
|
"RetryInterval" : "10",
|
2010-03-26 03:56:10 +01:00
|
|
|
"RetryDuration" : "20",
|
|
|
|
"configName" : "dummy-test"
|
2009-11-11 03:01:13 +01:00
|
|
|
},
|
2010-01-21 09:15:19 +01:00
|
|
|
"source/addressbook" : { "sync" : "slow",
|
2009-11-11 03:01:13 +01:00
|
|
|
"type" : "addressbook",
|
|
|
|
"uri" : "card"
|
|
|
|
},
|
|
|
|
"source/calendar" : { "sync" : "disabled",
|
|
|
|
"type" : "calendar",
|
|
|
|
"uri" : "cal"
|
2009-11-13 07:57:01 +01:00
|
|
|
},
|
2010-01-21 09:15:19 +01:00
|
|
|
"source/todo" : { "sync" : "disabled",
|
2009-11-13 07:57:01 +01:00
|
|
|
"type" : "todo",
|
|
|
|
"uri" : "task"
|
|
|
|
},
|
2010-01-21 09:15:19 +01:00
|
|
|
"source/memo" : { "sync" : "disabled",
|
2009-11-13 07:57:01 +01:00
|
|
|
"type" : "memo",
|
|
|
|
"uri" : "text"
|
2009-11-11 03:01:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
# update config
|
|
|
|
self.updateConfig = {
|
2009-12-25 09:33:23 +01:00
|
|
|
"" : { "username" : "doe"},
|
2009-11-11 03:01:13 +01:00
|
|
|
"source/addressbook" : { "sync" : "slow"}
|
|
|
|
}
|
2009-11-13 07:57:01 +01:00
|
|
|
self.sources = ['addressbook', 'calendar', 'todo', 'memo']
|
2009-11-11 03:01:13 +01:00
|
|
|
|
2010-02-02 05:52:40 +01:00
|
|
|
#create default or user settings evolutionsource
|
|
|
|
updateProps = self.getEvolutionSources(self.config)
|
|
|
|
for key, dict in updateProps.items():
|
|
|
|
self.config[key]["evolutionsource"] = dict["evolutionsource"]
|
|
|
|
|
2009-11-11 03:01:13 +01:00
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result)
|
|
|
|
|
|
|
|
def clearAllConfig(self):
|
2009-11-17 08:01:49 +01:00
|
|
|
""" clear a server config. All should be removed. Used internally. """
|
2009-11-11 03:01:13 +01:00
|
|
|
emptyConfig = {}
|
|
|
|
self.session.SetConfig(False, False, emptyConfig, utf8_strings=True)
|
|
|
|
|
|
|
|
def setupConfig(self):
|
2009-11-17 08:01:49 +01:00
|
|
|
""" create a server with full config. Used internally. """
|
2009-11-11 03:01:13 +01:00
|
|
|
self.session.SetConfig(False, False, self.config, utf8_strings=True)
|
|
|
|
|
|
|
|
def testSetConfigInvalidParam(self):
|
2009-11-17 08:01:49 +01:00
|
|
|
""" test that the right error is reported when parameters are not correct """
|
2009-11-11 03:01:13 +01:00
|
|
|
try:
|
|
|
|
self.session.SetConfig(False, True, {}, utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
|
|
|
"org.syncevolution.Exception: Clearing existing configuration "
|
|
|
|
"and temporary configuration changes which only affects the "
|
|
|
|
"duration of the session are mutually exclusive")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-11 03:01:13 +01:00
|
|
|
|
|
|
|
def testCreateGetConfig(self):
|
2009-11-17 08:01:49 +01:00
|
|
|
""" test the config is created successfully. """
|
2010-02-01 10:23:43 +01:00
|
|
|
self.config[""]["username"] = "creategetconfig"
|
|
|
|
self.config[""]["password"] = "112233445566778"
|
2009-11-11 03:01:13 +01:00
|
|
|
self.setupConfig()
|
|
|
|
""" get config and compare """
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config, self.config)
|
|
|
|
|
|
|
|
def testUpdateConfig(self):
|
2009-11-17 08:01:49 +01:00
|
|
|
""" test the config is permenantly updated correctly. """
|
2009-11-11 03:01:13 +01:00
|
|
|
self.setupConfig()
|
2009-11-17 08:01:49 +01:00
|
|
|
""" update the given config """
|
2009-11-11 03:01:13 +01:00
|
|
|
self.session.SetConfig(True, False, self.updateConfig, utf8_strings=True)
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
2009-12-25 09:33:23 +01:00
|
|
|
self.failUnlessEqual(config[""]["username"], "doe")
|
2009-11-11 03:01:13 +01:00
|
|
|
self.failUnlessEqual(config["source/addressbook"]["sync"], "slow")
|
|
|
|
|
|
|
|
def testUpdateConfigTemp(self):
|
2009-11-17 08:01:49 +01:00
|
|
|
""" test the config is just temporary updated but no effect in storage. """
|
2009-11-11 03:01:13 +01:00
|
|
|
self.setupConfig()
|
|
|
|
""" set config temporary """
|
|
|
|
self.session.SetConfig(True, True, self.updateConfig, utf8_strings=True)
|
2009-11-26 09:37:19 +01:00
|
|
|
self.session.Detach()
|
|
|
|
""" creat a new session to lose the temporary configs """
|
|
|
|
self.setUpSession("dummy-test")
|
2009-11-11 03:01:13 +01:00
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
""" no change of any properties """
|
|
|
|
self.failUnlessEqual(config, self.config)
|
|
|
|
|
2009-11-26 09:37:19 +01:00
|
|
|
def testGetConfigUpdateConfigTemp(self):
|
|
|
|
""" test the config is temporary updated and in effect for GetConfig in the current session. """
|
|
|
|
self.setupConfig()
|
|
|
|
""" set config temporary """
|
|
|
|
self.session.SetConfig(True, True, self.updateConfig, utf8_strings=True)
|
|
|
|
""" GetConfig is affected """
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
""" no change of any properties """
|
2009-12-25 09:33:23 +01:00
|
|
|
self.failUnlessEqual(config[""]["username"], "doe")
|
2009-11-26 09:37:19 +01:00
|
|
|
self.failUnlessEqual(config["source/addressbook"]["sync"], "slow")
|
|
|
|
|
2009-12-25 09:17:58 +01:00
|
|
|
def testGetConfigWithTempConfig(self):
|
|
|
|
""" test the config is gotten for a new temporary config. """
|
|
|
|
""" The given config doesn't exist on disk and it's set temporarily. Then GetConfig should
|
|
|
|
return the configs temporarily set. """
|
|
|
|
self.session.SetConfig(True, True, self.config, utf8_strings=True)
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config, self.config)
|
|
|
|
|
2009-11-11 03:01:13 +01:00
|
|
|
def testUpdateConfigError(self):
|
|
|
|
""" test the right error is reported when an invalid property value is set """
|
|
|
|
self.setupConfig()
|
|
|
|
config = {
|
|
|
|
"source/addressbook" : { "sync" : "invalid-value"}
|
|
|
|
}
|
|
|
|
try:
|
|
|
|
self.session.SetConfig(True, False, config, utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"org.syncevolution.Exception: test-dbus/config/syncevolution/default/peers/"
|
2009-11-18 07:28:12 +01:00
|
|
|
"dummy-test/sources/addressbook/config.ini: "
|
2009-11-11 03:01:13 +01:00
|
|
|
"sync = invalid-value: not one of the valid values (two-way, "
|
|
|
|
"slow, refresh-from-client = refresh-client, refresh-from-server "
|
|
|
|
"= refresh-server = refresh, one-way-from-client = one-way-client, "
|
|
|
|
"one-way-from-server = one-way-server = one-way, disabled = none)")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-11 03:01:13 +01:00
|
|
|
|
|
|
|
def testUpdateNoConfig(self):
|
|
|
|
""" test the right error is reported when updating properties for a non-existing server """
|
|
|
|
try:
|
|
|
|
self.session.SetConfig(True, False, self.updateConfig, utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"org.syncevolution.NoSuchConfig: The configuration 'dummy-test' doesn't exist")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
|
|
|
|
2009-11-11 03:01:13 +01:00
|
|
|
def testClearAllConfig(self):
|
2009-11-17 08:01:49 +01:00
|
|
|
""" test all configs of a server are cleared correctly. """
|
2009-11-11 03:01:13 +01:00
|
|
|
""" first set up config and then clear all configs and also check a non-existing config """
|
|
|
|
self.setupConfig()
|
|
|
|
self.clearAllConfig()
|
|
|
|
try:
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
2009-11-20 13:23:15 +01:00
|
|
|
"org.syncevolution.NoSuchConfig: No configuration 'dummy-test' found")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-11 03:01:13 +01:00
|
|
|
|
2009-11-13 07:57:01 +01:00
|
|
|
def testCheckSourceNoConfig(self):
|
|
|
|
""" test the right error is reported when the server doesn't exist """
|
|
|
|
try:
|
|
|
|
self.session.CheckSource("", utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
2009-11-20 13:21:04 +01:00
|
|
|
"org.syncevolution.NoSuchSource: 'dummy-test' has no '' source")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-13 07:57:01 +01:00
|
|
|
|
|
|
|
def testCheckSourceNoSourceName(self):
|
|
|
|
""" test the right error is reported when the source doesn't exist """
|
|
|
|
self.setupConfig()
|
|
|
|
try:
|
|
|
|
self.session.CheckSource("dummy", utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
2009-11-18 07:28:12 +01:00
|
|
|
"org.syncevolution.NoSuchSource: 'dummy-test' "
|
2009-11-13 07:57:01 +01:00
|
|
|
"has no 'dummy' source")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-13 07:57:01 +01:00
|
|
|
|
|
|
|
def testCheckSourceInvalidEvolutionSource(self):
|
|
|
|
""" test the right error is reported when the evolutionsource is invalid """
|
|
|
|
self.setupConfig()
|
|
|
|
config = { "source/memo" : { "evolutionsource" : "impossible-source"} }
|
|
|
|
self.session.SetConfig(True, False, config, utf8_strings=True)
|
|
|
|
try:
|
|
|
|
self.session.CheckSource("memo", utf8_strings=True)
|
2009-12-02 15:05:36 +01:00
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
|
|
|
"org.syncevolution.SourceUnusable: The source 'memo' is not usable")
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
|
|
|
|
|
|
|
def testCheckSourceInvalidType(self):
|
|
|
|
""" test the right error is reported when the type is invalid """
|
|
|
|
self.setupConfig()
|
|
|
|
# we cannot set an invalid type here, so pick one which is likely
|
|
|
|
# to be not supported in syncevo-dbus-server
|
|
|
|
config = { "source/memo" : { "type" : "apple-contacts"} }
|
|
|
|
self.session.SetConfig(True, False, config, utf8_strings=True)
|
|
|
|
try:
|
|
|
|
self.session.CheckSource("memo", utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
|
|
|
"org.syncevolution.SourceUnusable: The source 'memo' is not usable")
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
|
|
|
|
|
|
|
def testCheckSourceNoType(self):
|
|
|
|
""" test the right error is reported when the type is unset """
|
|
|
|
self.setupConfig()
|
|
|
|
config = { "source/memo" : { "type" : "" } }
|
|
|
|
self.session.SetConfig(True, False, config, utf8_strings=True)
|
|
|
|
try:
|
|
|
|
self.session.CheckSource("memo", utf8_strings=True)
|
2009-11-13 07:57:01 +01:00
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
2009-12-01 15:09:16 +01:00
|
|
|
"org.syncevolution.SourceUnusable: The source 'memo' is not usable")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-13 07:57:01 +01:00
|
|
|
|
|
|
|
def testCheckSource(self):
|
|
|
|
""" test all are right """
|
|
|
|
self.setupConfig()
|
2009-11-20 15:29:25 +01:00
|
|
|
for source in self.sources:
|
|
|
|
self.session.CheckSource(source, utf8_strings=True)
|
2009-11-13 07:57:01 +01:00
|
|
|
|
2009-11-26 09:37:19 +01:00
|
|
|
def testCheckSourceUpdateConfigTemp(self):
|
|
|
|
""" test the config is temporary updated and in effect for GetDatabases in the current session. """
|
|
|
|
self.setupConfig()
|
|
|
|
tempConfig = {"source/temp" : { "type" : "calendar"}}
|
|
|
|
self.session.SetConfig(True, True, tempConfig, utf8_strings=True)
|
|
|
|
databases2 = self.session.CheckSource("temp", utf8_strings=True)
|
|
|
|
|
2009-11-13 08:05:00 +01:00
|
|
|
def testGetDatabasesNoConfig(self):
|
|
|
|
""" test the right error is reported when the server doesn't exist """
|
|
|
|
# make sure the config doesn't exist """
|
|
|
|
try:
|
|
|
|
self.session.GetDatabases("", utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
2009-11-20 15:29:25 +01:00
|
|
|
"org.syncevolution.NoSuchSource: 'dummy-test' has no '' source")
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-13 08:05:00 +01:00
|
|
|
|
|
|
|
def testGetDatabasesEmpty(self):
|
2009-11-20 15:29:25 +01:00
|
|
|
""" test the right error is reported for non-existing source"""
|
2009-11-13 08:05:00 +01:00
|
|
|
self.setupConfig()
|
2009-11-20 15:29:25 +01:00
|
|
|
try:
|
|
|
|
databases = self.session.GetDatabases("never_use_this_source_name", utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
|
|
|
"org.syncevolution.NoSuchSource: 'dummy-test' has no 'never_use_this_source_name' source")
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-13 08:05:00 +01:00
|
|
|
|
|
|
|
def testGetDatabases(self):
|
|
|
|
""" test the right way to get databases """
|
|
|
|
self.setupConfig()
|
|
|
|
|
|
|
|
# don't know actual databases, so compare results of two different times
|
2009-11-20 15:29:25 +01:00
|
|
|
sources = ['addressbook', 'calendar', 'todo', 'memo']
|
2009-11-13 08:05:00 +01:00
|
|
|
databases1 = []
|
|
|
|
for source in sources:
|
|
|
|
databases1.append(self.session.GetDatabases(source, utf8_strings=True))
|
|
|
|
# reverse the list of sources and get databases again
|
|
|
|
sources.reverse()
|
|
|
|
databases2 = []
|
|
|
|
for source in sources:
|
|
|
|
databases2.append(self.session.GetDatabases(source, utf8_strings=True))
|
|
|
|
# sort two arrays
|
|
|
|
databases1.sort()
|
|
|
|
databases2.sort()
|
|
|
|
self.failUnlessEqual(databases1, databases2)
|
|
|
|
|
2009-11-26 09:37:19 +01:00
|
|
|
def testGetDatabasesUpdateConfigTemp(self):
|
|
|
|
""" test the config is temporary updated and in effect for GetDatabases in the current session. """
|
|
|
|
self.setupConfig()
|
|
|
|
databases1 = self.session.GetDatabases("calendar", utf8_strings=True)
|
|
|
|
tempConfig = {"source/temp" : { "type" : "calendar"}}
|
|
|
|
self.session.SetConfig(True, True, tempConfig, utf8_strings=True)
|
|
|
|
databases2 = self.session.GetDatabases("temp", utf8_strings=True)
|
|
|
|
self.failUnlessEqual(databases2, databases1)
|
|
|
|
|
2009-11-14 09:45:51 +01:00
|
|
|
def testGetReportsNoConfig(self):
|
2009-11-18 08:11:56 +01:00
|
|
|
""" Test nothing is gotten when the given server doesn't exist. Also covers boundaries """
|
2009-11-14 09:45:51 +01:00
|
|
|
reports = self.session.GetReports(0, 0, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
reports = self.session.GetReports(0, 1, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
reports = self.session.GetReports(0, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
reports = self.session.GetReports(0xFFFFFFFF, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
|
|
|
|
def testGetReportsNoReports(self):
|
|
|
|
""" Test when the given server has no reports. Also covers boundaries """
|
|
|
|
self.setupConfig()
|
|
|
|
reports = self.session.GetReports(0, 0, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
reports = self.session.GetReports(0, 1, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
reports = self.session.GetReports(0, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
reports = self.session.GetReports(0xFFFFFFFF, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
|
2009-11-18 10:11:55 +01:00
|
|
|
def testGetReportsByRef(self):
|
|
|
|
""" Test the reports are gotten correctly from reference files. Also covers boundaries """
|
|
|
|
""" This could be extractly compared since the reference files are known """
|
2009-11-20 14:01:34 +01:00
|
|
|
self.setupFiles('reports')
|
2009-12-03 10:37:00 +01:00
|
|
|
report0 = { "peer" : "dummy-test",
|
|
|
|
"start" : "1258520955",
|
|
|
|
"end" : "1258520964",
|
2009-11-25 10:01:06 +01:00
|
|
|
"status" : "200",
|
|
|
|
"source-addressbook-mode" : "slow",
|
|
|
|
"source-addressbook-first" : "true",
|
|
|
|
"source-addressbook-resume" : "false",
|
|
|
|
"source-addressbook-status" : "0",
|
|
|
|
"source-addressbook-backup-before" : "0",
|
|
|
|
"source-addressbook-backup-after" : "0",
|
|
|
|
"source-addressbook-stat-local-any-sent" : "9168",
|
2009-11-18 10:11:55 +01:00
|
|
|
"source-addressbook-stat-remote-added-total" : "71",
|
|
|
|
"source-addressbook-stat-remote-updated-total" : "100",
|
|
|
|
"source-addressbook-stat-local-updated-total" : "632",
|
|
|
|
"source-addressbook-stat-remote-any-reject" : "100",
|
|
|
|
"source-addressbook-stat-remote-any-conflict_duplicated" : "5293487",
|
|
|
|
"source-addressbook-stat-remote-any-conflict_client_won" : "33",
|
|
|
|
"source-addressbook-stat-local-any-received" : "2",
|
|
|
|
"source-addressbook-stat-local-removed-total" : "4",
|
|
|
|
"source-addressbook-stat-remote-any-conflict_server_won" : "38",
|
|
|
|
"source-addressbook-stat-local-any-reject" : "77",
|
|
|
|
"source-addressbook-stat-local-added-total" : "84",
|
|
|
|
"source-addressbook-stat-remote-removed-total" : "66",
|
2009-11-25 10:01:06 +01:00
|
|
|
"source-calendar-mode" : "slow",
|
|
|
|
"source-calendar-first" : "true",
|
|
|
|
"source-calendar-resume" : "false",
|
|
|
|
"source-calendar-status" : "0",
|
|
|
|
"source-calendar-backup-before" : "17",
|
|
|
|
"source-calendar-backup-after" : "17",
|
2009-11-18 10:11:55 +01:00
|
|
|
"source-calendar-stat-local-any-sent" : "8619",
|
|
|
|
"source-calendar-stat-remote-added-total": "17",
|
|
|
|
"source-calendar-stat-remote-updated-total" : "10",
|
|
|
|
"source-calendar-stat-local-updated-total" : "6",
|
|
|
|
"source-calendar-stat-remote-any-reject" : "1",
|
|
|
|
"source-calendar-stat-remote-any-conflict_duplicated" : "5",
|
|
|
|
"source-calendar-stat-remote-any-conflict_client_won" : "3",
|
|
|
|
"source-calendar-stat-local-any-received" : "24",
|
|
|
|
"source-calendar-stat-local-removed-total" : "54",
|
|
|
|
"source-calendar-stat-remote-any-conflict_server_won" : "38",
|
|
|
|
"source-calendar-stat-local-any-reject" : "7",
|
|
|
|
"source-calendar-stat-local-added-total" : "42",
|
|
|
|
"source-calendar-stat-remote-removed-total" : "6",
|
2009-11-25 10:01:06 +01:00
|
|
|
"source-memo-mode" : "slow",
|
|
|
|
"source-memo-first" : "true",
|
|
|
|
"source-memo-resume" : "false",
|
|
|
|
"source-memo-status" : "0",
|
|
|
|
"source-memo-backup-before" : "3",
|
|
|
|
"source-memo-backup-after" : "4",
|
2009-11-18 10:11:55 +01:00
|
|
|
"source-memo-stat-local-any-sent" : "8123",
|
|
|
|
"source-memo-stat-remote-added-total" : "15",
|
|
|
|
"source-memo-stat-remote-updated-total" : "6",
|
|
|
|
"source-memo-stat-local-updated-total" : "8",
|
|
|
|
"source-memo-stat-remote-any-reject" : "16",
|
|
|
|
"source-memo-stat-remote-any-conflict_duplicated" : "27",
|
|
|
|
"source-memo-stat-remote-any-conflict_client_won" : "2",
|
|
|
|
"source-memo-stat-local-any-received" : "3",
|
|
|
|
"source-memo-stat-local-removed-total" : "4",
|
|
|
|
"source-memo-stat-remote-any-conflict_server_won" : "8",
|
|
|
|
"source-memo-stat-local-any-reject" : "40",
|
|
|
|
"source-memo-stat-local-added-total" : "34",
|
|
|
|
"source-memo-stat-remote-removed-total" : "5",
|
2009-11-25 10:01:06 +01:00
|
|
|
"source-todo-mode" : "slow",
|
|
|
|
"source-todo-first" : "true",
|
|
|
|
"source-todo-resume" : "false",
|
|
|
|
"source-todo-status" : "0",
|
|
|
|
"source-todo-backup-before" : "2",
|
|
|
|
"source-todo-backup-after" : "2",
|
2009-11-18 10:11:55 +01:00
|
|
|
"source-todo-stat-local-any-sent" : "619",
|
|
|
|
"source-todo-stat-remote-added-total" : "71",
|
|
|
|
"source-todo-stat-remote-updated-total" : "1",
|
|
|
|
"source-todo-stat-local-updated-total" : "9",
|
|
|
|
"source-todo-stat-remote-any-reject" : "10",
|
|
|
|
"source-todo-stat-remote-any-conflict_duplicated" : "15",
|
|
|
|
"source-todo-stat-remote-any-conflict_client_won" : "7",
|
|
|
|
"source-todo-stat-local-any-received" : "2",
|
|
|
|
"source-todo-stat-local-removed-total" : "4",
|
|
|
|
"source-todo-stat-remote-any-conflict_server_won" : "8",
|
|
|
|
"source-todo-stat-local-any-reject" : "3",
|
|
|
|
"source-todo-stat-local-added-total" : "24",
|
|
|
|
"source-todo-stat-remote-removed-total" : "80" }
|
|
|
|
reports = self.session.GetReports(0, 0, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
# get only one report
|
|
|
|
reports = self.session.GetReports(0, 1, utf8_strings=True)
|
|
|
|
self.assertTrue(len(reports) == 1)
|
2009-12-15 04:11:38 +01:00
|
|
|
del reports[0]["dir"]
|
2009-12-03 10:37:00 +01:00
|
|
|
|
2009-11-18 10:11:55 +01:00
|
|
|
self.failUnlessEqual(reports[0], report0)
|
|
|
|
""" the number of reference sessions is totally 5. Check the returned count
|
|
|
|
when parameter is bigger than 5 """
|
|
|
|
reports = self.session.GetReports(0, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.assertTrue(len(reports) == 5)
|
|
|
|
# start from 2, this could check integer overflow
|
|
|
|
reports2 = self.session.GetReports(2, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.assertTrue(len(reports2) == 3)
|
|
|
|
# the first element of reports2 should be the same as the third element of reports
|
|
|
|
self.failUnlessEqual(reports[2], reports2[0])
|
|
|
|
# indexed from 5, nothing could be gotten
|
|
|
|
reports = self.session.GetReports(5, 0xFFFFFFFF, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(reports, [])
|
|
|
|
|
2009-12-21 08:02:24 +01:00
|
|
|
def testRestoreByRef(self):
|
|
|
|
# test when the data before or after a given session is restored
|
2009-12-23 04:04:12 +01:00
|
|
|
self.setupFiles('restore')
|
2009-12-21 08:02:24 +01:00
|
|
|
self.setupConfig()
|
|
|
|
self.setUpListeners(self.sessionpath)
|
|
|
|
reports = self.session.GetReports(0, 1, utf8_strings=True)
|
|
|
|
dir = reports[0]["dir"]
|
|
|
|
sessionpath, session = self.createSession("dummy-test", False)
|
|
|
|
#TODO: check restore result, how?
|
|
|
|
#restore data before this session
|
|
|
|
self.session.Restore(dir, True, [], utf8_strings=True)
|
|
|
|
loop.run()
|
|
|
|
self.session.Detach()
|
2009-12-22 09:47:31 +01:00
|
|
|
|
|
|
|
# check recorded events in DBusUtil.events, first filter them
|
|
|
|
statuses = []
|
|
|
|
progresses = []
|
|
|
|
for item in DBusUtil.events:
|
|
|
|
if item[0] == "status":
|
|
|
|
statuses.append(item[1])
|
|
|
|
elif item[0] == "progress":
|
|
|
|
progresses.append(item[1])
|
|
|
|
|
|
|
|
lastStatus = ""
|
|
|
|
lastSources = {}
|
|
|
|
statusPairs = {"": 0, "idle": 1, "running" : 2, "done" : 3}
|
|
|
|
for status, error, sources in statuses:
|
|
|
|
self.failIf(status == lastStatus and lastSources == sources)
|
|
|
|
# no error
|
|
|
|
self.failUnlessEqual(error, 0)
|
|
|
|
for sourcename, value in sources.items():
|
|
|
|
# no error
|
|
|
|
self.failUnlessEqual(value[2], 0)
|
|
|
|
# keep order: source status must also be unchanged or the next status
|
|
|
|
if lastSources.has_key(sourcename):
|
|
|
|
lastValue = lastSources[sourcename]
|
|
|
|
self.failUnless(statusPairs[value[1]] >= statusPairs[lastValue[1]])
|
|
|
|
|
|
|
|
lastStatus = status
|
|
|
|
lastSources = sources
|
|
|
|
|
|
|
|
# check increasing progress percentage
|
|
|
|
lastPercent = 0
|
|
|
|
for percent, sources in progresses:
|
|
|
|
self.failIf(percent < lastPercent)
|
|
|
|
lastPercent = percent
|
|
|
|
|
2009-12-21 08:02:24 +01:00
|
|
|
session.SetConfig(False, False, self.config, utf8_strings=True)
|
|
|
|
#restore data after this session
|
|
|
|
session.Restore(dir, False, ["addressbook", "calendar"], utf8_strings=True)
|
|
|
|
loop.run()
|
|
|
|
|
|
|
|
def testSecondRestore(self):
|
|
|
|
# test the right error is thrown when session is not active
|
2009-12-23 04:04:12 +01:00
|
|
|
self.setupFiles('restore')
|
2009-12-21 08:02:24 +01:00
|
|
|
self.setupConfig()
|
|
|
|
self.setUpListeners(self.sessionpath)
|
|
|
|
reports = self.session.GetReports(0, 1, utf8_strings=True)
|
|
|
|
dir = reports[0]["dir"]
|
|
|
|
sessionpath, session = self.createSession("dummy-test", False)
|
|
|
|
try:
|
|
|
|
session.Restore(dir, False, [], utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
|
|
|
"org.syncevolution.InvalidCall: session is not active, call not allowed at this time")
|
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
|
|
|
|
|
|
|
self.session.Detach()
|
|
|
|
session.SetConfig(False, False, self.config, utf8_strings=True)
|
|
|
|
session.Restore(dir, False, [], utf8_strings=True)
|
|
|
|
loop.run()
|
|
|
|
|
2009-12-24 08:07:48 +01:00
|
|
|
@timeout(300)
|
|
|
|
def testInteractivePassword(self):
|
|
|
|
""" test the info request is correctly working for password """
|
|
|
|
self.setupConfig()
|
|
|
|
self.setUpListeners(self.sessionpath)
|
|
|
|
self.lastState = "unknown"
|
|
|
|
# define callback for InfoRequest signals and send corresponds response
|
|
|
|
# to dbus server
|
|
|
|
def infoRequest(id, session, state, handler, type, params):
|
|
|
|
if state == "request":
|
|
|
|
self.failUnlessEqual(self.lastState, "unknown")
|
|
|
|
self.lastState = "request"
|
|
|
|
self.server.InfoResponse(id, "working", {}, utf8_strings=True)
|
|
|
|
elif state == "waiting":
|
|
|
|
self.failUnlessEqual(self.lastState, "request")
|
|
|
|
self.lastState = "waiting"
|
|
|
|
self.server.InfoResponse(id, "response", {"password" : "123456"}, utf8_strings=True)
|
|
|
|
elif state == "done":
|
|
|
|
self.failUnlessEqual(self.lastState, "waiting")
|
|
|
|
self.lastState = "done"
|
|
|
|
else:
|
|
|
|
self.fail("state should not be '" + state + "'")
|
|
|
|
|
|
|
|
signal = bus.add_signal_receiver(infoRequest,
|
|
|
|
'InfoRequest',
|
|
|
|
'org.syncevolution.Server',
|
|
|
|
'org.syncevolution',
|
|
|
|
None,
|
|
|
|
byte_arrays=True,
|
|
|
|
utf8_strings=True)
|
|
|
|
|
|
|
|
# dbus server will be blocked by gnome-keyring-ask dialog, so we kill it, and then
|
|
|
|
# it can't get the password from gnome keyring and send info request for password
|
|
|
|
def callback():
|
|
|
|
kill = subprocess.Popen("sh -c 'killall -9 gnome-keyring-ask >/dev/null 2>&1'", shell=True)
|
|
|
|
kill.communicate()
|
|
|
|
return True
|
|
|
|
|
|
|
|
timeout_handler = Timeout.addTimeout(1, callback)
|
|
|
|
|
|
|
|
# try to sync and invoke password request
|
|
|
|
self.session.Sync("", {})
|
|
|
|
loop.run()
|
|
|
|
Timeout.removeTimeout(timeout_handler)
|
|
|
|
self.failUnlessEqual(self.lastState, "done")
|
|
|
|
|
2009-11-14 09:45:51 +01:00
|
|
|
class TestSessionAPIsReal(unittest.TestCase, DBusUtil):
|
|
|
|
""" This class is used to test those unit tests of session APIs, depending on doing sync.
|
|
|
|
Thus we need a real server configuration to confirm sync could be run successfully.
|
|
|
|
Typically we need make sure that at least one sync has been done before testing our
|
|
|
|
desired unit tests. Note that it also covers session.Sync API itself """
|
2010-01-21 09:15:19 +01:00
|
|
|
""" All unit tests in this class have a dependency on a real sync config named 'dbus_unittest',
|
|
|
|
which should works correctly. """
|
2009-10-14 17:59:03 +02:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setUpSession(configName)
|
2009-12-01 03:15:20 +01:00
|
|
|
self.operation = ""
|
2009-10-14 17:59:03 +02:00
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result, own_xdg=False)
|
|
|
|
|
2010-01-21 09:15:19 +01:00
|
|
|
def setupConfig(self):
|
|
|
|
""" Apply for user settings. Used internally. """
|
|
|
|
configProps = { }
|
|
|
|
# check whether 'dbus_unittest' is configured.
|
|
|
|
try:
|
|
|
|
configProps = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.fail(str(ex) +
|
|
|
|
". To test this case, please first set up a correct config named 'dbus_unittest'.")
|
2010-02-02 05:52:40 +01:00
|
|
|
updateProps = self.getEvolutionSources(configProps)
|
|
|
|
# temporarily set evolutionsource and don't change them
|
|
|
|
self.session.SetConfig(True, True, updateProps, utf8_strings=True)
|
2010-01-21 09:15:19 +01:00
|
|
|
|
2009-11-14 09:45:51 +01:00
|
|
|
def doSync(self):
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setupConfig()
|
2009-11-05 18:04:41 +01:00
|
|
|
self.setUpListeners(self.sessionpath)
|
2010-03-30 08:16:57 +02:00
|
|
|
self.session.Sync("slow", {})
|
2009-10-14 17:59:03 +02:00
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
2009-11-14 09:45:51 +01:00
|
|
|
|
2009-12-01 03:15:20 +01:00
|
|
|
def progressChanged(self, *args):
|
|
|
|
# subclass specifies its own callback for ProgressChanged signals
|
|
|
|
percentage = args[0]
|
|
|
|
# make sure sync is really running
|
|
|
|
if percentage > 20:
|
|
|
|
if self.operation == "abort":
|
|
|
|
self.session.Abort()
|
|
|
|
if self.operation == "suspend":
|
|
|
|
self.session.Suspend()
|
|
|
|
|
2009-11-18 22:39:43 +01:00
|
|
|
@timeout(300)
|
2009-11-14 09:45:51 +01:00
|
|
|
def testSync(self):
|
2009-12-01 03:15:20 +01:00
|
|
|
'''run a real sync with default server and test status list and progress number'''
|
|
|
|
''' check events list is correct for StatusChanged and ProgressChanged '''
|
|
|
|
# do sync
|
2009-11-14 09:45:51 +01:00
|
|
|
self.doSync()
|
2009-12-01 03:15:20 +01:00
|
|
|
|
|
|
|
# check recorded events in DBusUtil.events, first filter them
|
|
|
|
statuses = []
|
|
|
|
progresses = []
|
|
|
|
# dict is used to maintain status order.
|
|
|
|
statusPairs = {"": 0, "idle": 1, "running" : 2, "done" : 3}
|
|
|
|
for item in DBusUtil.events:
|
|
|
|
if item[0] == "status":
|
|
|
|
statuses.append(item[1])
|
|
|
|
elif item[0] == "progress":
|
|
|
|
progresses.append(item[1])
|
|
|
|
|
|
|
|
# check statuses
|
|
|
|
lastStatus = ""
|
|
|
|
lastSources = {}
|
|
|
|
for status, error, sources in statuses:
|
|
|
|
self.failIf(status == lastStatus and lastSources == sources)
|
|
|
|
# no error
|
|
|
|
self.failUnlessEqual(error, 0)
|
|
|
|
# keep order: session status must be unchanged or the next status
|
2009-12-07 07:26:01 +01:00
|
|
|
seps = status.split(';')
|
|
|
|
lastSeps = lastStatus.split(';')
|
|
|
|
self.failUnless(statusPairs.has_key(seps[0]))
|
|
|
|
self.failUnless(statusPairs[seps[0]] >= statusPairs[lastSeps[0]])
|
|
|
|
# check specifiers
|
|
|
|
if len(seps) > 1:
|
|
|
|
self.failUnlessEqual(seps[1], "waiting")
|
2009-12-01 03:15:20 +01:00
|
|
|
for sourcename, value in sources.items():
|
|
|
|
# no error
|
|
|
|
self.failUnlessEqual(value[2], 0)
|
|
|
|
# keep order: source status must also be unchanged or the next status
|
|
|
|
if lastSources.has_key(sourcename):
|
|
|
|
lastValue = lastSources[sourcename]
|
|
|
|
self.failUnless(statusPairs[value[1]] >= statusPairs[lastValue[1]])
|
|
|
|
|
|
|
|
lastStatus = status
|
|
|
|
lastSources = sources
|
|
|
|
|
|
|
|
# check increasing progress percentage
|
|
|
|
lastPercent = 0
|
|
|
|
for percent, sources in progresses:
|
|
|
|
self.failIf(percent < lastPercent)
|
|
|
|
lastPercent = percent
|
|
|
|
|
2009-10-14 17:59:03 +02:00
|
|
|
status, error, sources = self.session.GetStatus(utf8_strings=True)
|
|
|
|
self.failUnlessEqual(status, "done")
|
|
|
|
self.failUnlessEqual(error, 0)
|
2009-12-01 03:15:20 +01:00
|
|
|
|
|
|
|
@timeout(300)
|
|
|
|
def testSyncStatusAbort(self):
|
|
|
|
''' test status is set correctly when the session is aborted '''
|
|
|
|
self.operation = "abort"
|
|
|
|
self.doSync()
|
|
|
|
hasAbortingStatus = False
|
|
|
|
for item in DBusUtil.events:
|
|
|
|
if item[0] == "status" and item[1][0] == "aborting":
|
|
|
|
hasAbortingStatus = True
|
|
|
|
break
|
|
|
|
self.failUnlessEqual(hasAbortingStatus, True)
|
|
|
|
|
|
|
|
@timeout(300)
|
|
|
|
def testSyncStatusSuspend(self):
|
|
|
|
''' test status is set correctly when the session is suspended '''
|
|
|
|
self.operation = "suspend"
|
|
|
|
self.doSync()
|
|
|
|
hasSuspendingStatus = False
|
|
|
|
for item in DBusUtil.events:
|
2009-12-07 07:26:01 +01:00
|
|
|
if item[0] == "status" and "suspending" in item[1][0] :
|
2009-12-01 03:15:20 +01:00
|
|
|
hasSuspendingStatus = True
|
|
|
|
break
|
|
|
|
self.failUnlessEqual(hasSuspendingStatus, True)
|
2009-11-05 18:04:41 +01:00
|
|
|
|
2009-11-19 11:43:12 +01:00
|
|
|
@timeout(300)
|
|
|
|
def testSyncSecondSession(self):
|
|
|
|
'''ask for a second session that becomes ready after a real sync'''
|
|
|
|
sessionpath2, session2 = self.createSession("", False)
|
|
|
|
status, error, sources = session2.GetStatus(utf8_strings=True)
|
|
|
|
self.failUnlessEqual(status, "queueing")
|
|
|
|
self.testSync()
|
|
|
|
# now wait for second session becoming ready
|
|
|
|
loop.run()
|
|
|
|
status, error, sources = session2.GetStatus(utf8_strings=True)
|
|
|
|
self.failUnlessEqual(status, "idle")
|
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done",
|
|
|
|
"session " + sessionpath2 + " ready"])
|
|
|
|
session2.Detach()
|
|
|
|
|
2009-11-10 14:20:01 +01:00
|
|
|
class TestDBusSyncError(unittest.TestCase, DBusUtil):
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setUpSession(configName)
|
2009-11-10 14:20:01 +01:00
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result, own_xdg=True)
|
|
|
|
|
2009-11-17 13:39:01 +01:00
|
|
|
def testSyncNoConfig(self):
|
|
|
|
"""Executes a real sync with no corresponding config."""
|
2009-11-10 14:20:01 +01:00
|
|
|
self.setUpListeners(self.sessionpath)
|
|
|
|
self.session.Sync("", {})
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
# TODO: check recorded events in DBusUtil.events
|
2009-11-10 14:20:01 +01:00
|
|
|
status, error, sources = self.session.GetStatus(utf8_strings=True)
|
|
|
|
self.failUnlessEqual(status, "done")
|
2010-01-21 09:15:19 +01:00
|
|
|
self.failUnlessEqual(error, 10500)
|
2009-11-10 14:20:01 +01:00
|
|
|
|
2009-11-05 18:04:41 +01:00
|
|
|
class TestConnection(unittest.TestCase, DBusUtil):
|
|
|
|
"""Tests Server.Connect(). Tests depend on getting one Abort signal to terminate."""
|
|
|
|
|
2009-11-11 10:55:15 +01:00
|
|
|
"""a real message sent to our own server, DevInf stripped, username/password foo/bar"""
|
2009-11-06 11:43:55 +01:00
|
|
|
message1 = '''<?xml version="1.0" encoding="UTF-8"?><SyncML xmlns='SYNCML:SYNCML1.2'><SyncHdr><VerDTD>1.2</VerDTD><VerProto>SyncML/1.2</VerProto><SessionID>255</SessionID><MsgID>1</MsgID><Target><LocURI>http://127.0.0.1:9000/syncevolution</LocURI></Target><Source><LocURI>sc-api-nat</LocURI><LocName>test</LocName></Source><Cred><Meta><Format xmlns='syncml:metinf'>b64</Format><Type xmlns='syncml:metinf'>syncml:auth-md5</Type></Meta><Data>kHzMn3RWFGWSKeBpXicppQ==</Data></Cred><Meta><MaxMsgSize xmlns='syncml:metinf'>20000</MaxMsgSize><MaxObjSize xmlns='syncml:metinf'>4000000</MaxObjSize></Meta></SyncHdr><SyncBody><Alert><CmdID>1</CmdID><Data>200</Data><Item><Target><LocURI>addressbook</LocURI></Target><Source><LocURI>./addressbook</LocURI></Source><Meta><Anchor xmlns='syncml:metinf'><Last>20091105T092757Z</Last><Next>20091105T092831Z</Next></Anchor><MaxObjSize xmlns='syncml:metinf'>4000000</MaxObjSize></Meta></Item></Alert><Final/></SyncBody></SyncML>'''
|
2009-11-05 18:04:41 +01:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
|
|
|
self.setUpListeners(None)
|
2010-01-21 09:15:19 +01:00
|
|
|
# default config
|
|
|
|
self.config = {
|
|
|
|
"" : { "remoteDeviceId" : "sc-api-nat",
|
|
|
|
"password" : "test",
|
|
|
|
"username" : "test",
|
|
|
|
"PeerIsClient" : "1",
|
|
|
|
"RetryInterval" : "1",
|
|
|
|
"RetryDuration" : "10"
|
|
|
|
},
|
|
|
|
"source/addressbook" : { "sync" : "two-way",
|
|
|
|
"type" : "addressbook",
|
|
|
|
"uri" : "card"
|
|
|
|
},
|
|
|
|
"source/calendar" : { "sync" : "two-way",
|
|
|
|
"type" : "calendar",
|
|
|
|
"uri" : "cal"
|
|
|
|
},
|
|
|
|
"source/todo" : { "sync" : "two-way",
|
|
|
|
"type" : "todo",
|
|
|
|
"uri" : "task"
|
|
|
|
},
|
|
|
|
"source/memo" : { "sync" : "two-way",
|
|
|
|
"type" : "memo",
|
|
|
|
"uri" : "text"
|
|
|
|
}
|
|
|
|
}
|
2010-02-02 05:52:40 +01:00
|
|
|
|
|
|
|
#create default or user settings evolutionsource
|
|
|
|
updateProps = self.getEvolutionSources(self.config)
|
|
|
|
for key, dict in updateProps.items():
|
|
|
|
self.config[key]["evolutionsource"] = dict["evolutionsource"]
|
|
|
|
|
2010-01-21 09:15:19 +01:00
|
|
|
def setupConfig(self, name="dummy-test", deviceId="sc-api-nat"):
|
|
|
|
self.setUpSession(name)
|
|
|
|
self.config[""]["remoteDeviceId"] = deviceId
|
|
|
|
self.session.SetConfig(False, False, self.config, utf8_strings=True)
|
|
|
|
self.session.Detach()
|
2009-11-05 18:04:41 +01:00
|
|
|
|
|
|
|
def run(self, result):
|
2010-01-21 09:15:19 +01:00
|
|
|
self.runTest(result, own_xdg=True)
|
2009-11-05 18:04:41 +01:00
|
|
|
|
2009-11-11 10:55:15 +01:00
|
|
|
def getConnection(self, must_authenticate=False):
|
2009-11-05 18:04:41 +01:00
|
|
|
conpath = self.server.Connect({'description': 'test-dbus.py',
|
|
|
|
'transport': 'dummy'},
|
2009-11-11 10:55:15 +01:00
|
|
|
must_authenticate,
|
2009-11-05 18:04:41 +01:00
|
|
|
"")
|
|
|
|
self.setUpConnectionListeners(conpath)
|
|
|
|
connection = dbus.Interface(bus.get_object('org.syncevolution',
|
|
|
|
conpath),
|
|
|
|
'org.syncevolution.Connection')
|
|
|
|
return (conpath, connection)
|
|
|
|
|
|
|
|
def testConnect(self):
|
|
|
|
"""get connection and close it"""
|
|
|
|
conpath, connection = self.getConnection()
|
|
|
|
connection.Close(False, 'good bye')
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.events, [('abort',)])
|
2009-11-05 18:04:41 +01:00
|
|
|
|
|
|
|
def testInvalidConnect(self):
|
|
|
|
"""get connection, send invalid initial message"""
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setupConfig()
|
2009-11-05 18:04:41 +01:00
|
|
|
conpath, connection = self.getConnection()
|
|
|
|
try:
|
|
|
|
connection.Process('1234', 'invalid message type')
|
|
|
|
except dbus.DBusException, ex:
|
|
|
|
self.failUnlessEqual(str(ex),
|
2010-08-25 10:30:24 +02:00
|
|
|
"org.syncevolution.Exception: message type 'invalid message type' not supported for starting a sync")
|
2009-11-20 15:29:25 +01:00
|
|
|
else:
|
|
|
|
self.fail("no exception thrown")
|
2009-11-05 18:04:41 +01:00
|
|
|
loop.run()
|
2010-01-21 09:15:19 +01:00
|
|
|
# 'idle' status doesn't be checked
|
|
|
|
self.failUnless(('abort',) in DBusUtil.events)
|
2009-11-05 18:04:41 +01:00
|
|
|
|
|
|
|
def testStartSync(self):
|
|
|
|
"""send a valid initial SyncML message"""
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setupConfig()
|
2009-11-05 18:04:41 +01:00
|
|
|
conpath, connection = self.getConnection()
|
|
|
|
connection.Process(TestConnection.message1, 'application/vnd.syncml+xml')
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " got reply"])
|
|
|
|
DBusUtil.quit_events = []
|
2009-11-05 18:04:41 +01:00
|
|
|
# TODO: check events
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failIfEqual(DBusUtil.reply, None)
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
|
2009-11-11 10:55:15 +01:00
|
|
|
# credentials should have been accepted because must_authenticate=False
|
|
|
|
# in Connect(); 508 = "refresh required" is normal
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnless('<Status><CmdID>2</CmdID><MsgRef>1</MsgRef><CmdRef>1</CmdRef><Cmd>Alert</Cmd><TargetRef>addressbook</TargetRef><SourceRef>./addressbook</SourceRef><Data>508</Data>' in DBusUtil.reply[0])
|
|
|
|
self.failIf('<Chal>' in DBusUtil.reply[0])
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[3], False)
|
|
|
|
self.failIfEqual(DBusUtil.reply[4], '')
|
2009-11-11 10:55:15 +01:00
|
|
|
connection.Close(False, 'good bye')
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " aborted",
|
|
|
|
"session done"])
|
2010-05-04 16:05:24 +02:00
|
|
|
# start another session for the server (ensures that the previous one is done),
|
|
|
|
# then check the server side report
|
|
|
|
DBusUtil.quit_events = []
|
|
|
|
self.setUpSession("dummy-test")
|
|
|
|
sessions = self.session.GetReports(0, 100)
|
|
|
|
self.failUnlessEqual(len(sessions), 1)
|
|
|
|
# transport failure, only addressbook active and later aborted
|
|
|
|
self.failUnlessEqual(sessions[0]["status"], "20043")
|
|
|
|
self.failUnlessEqual(sessions[0]["error"], "D-Bus peer has disconnected")
|
|
|
|
self.failUnlessEqual(sessions[0]["source-addressbook-status"], "20017")
|
|
|
|
self.failUnlessEqual(sessions[0]["source-calendar-status"], "0")
|
|
|
|
self.failUnlessEqual(sessions[0]["source-todo-status"], "0")
|
|
|
|
self.failUnlessEqual(sessions[0]["source-memo-status"], "0")
|
|
|
|
|
2009-11-11 10:55:15 +01:00
|
|
|
|
|
|
|
def testCredentialsWrong(self):
|
|
|
|
"""send invalid credentials"""
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setupConfig()
|
2009-11-11 10:55:15 +01:00
|
|
|
conpath, connection = self.getConnection(must_authenticate=True)
|
|
|
|
connection.Process(TestConnection.message1, 'application/vnd.syncml+xml')
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " got reply"])
|
|
|
|
DBusUtil.quit_events = []
|
2009-11-11 10:55:15 +01:00
|
|
|
# TODO: check events
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failIfEqual(DBusUtil.reply, None)
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
|
2009-11-11 10:55:15 +01:00
|
|
|
# credentials should have been rejected because of wrong Nonce
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnless('<Chal>' in DBusUtil.reply[0])
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[3], False)
|
|
|
|
self.failIfEqual(DBusUtil.reply[4], '')
|
2009-11-11 10:55:15 +01:00
|
|
|
connection.Close(False, 'good bye')
|
|
|
|
# when the login fails, the server also ends the session
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
DBusUtil.quit_events.sort()
|
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " aborted",
|
|
|
|
"connection " + conpath + " got final reply",
|
|
|
|
"session done"])
|
2009-11-11 10:55:15 +01:00
|
|
|
|
|
|
|
def testCredentialsRight(self):
|
|
|
|
"""send correct credentials"""
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setupConfig()
|
2009-11-11 10:55:15 +01:00
|
|
|
conpath, connection = self.getConnection(must_authenticate=True)
|
|
|
|
plain_auth = TestConnection.message1.replace("<Type xmlns='syncml:metinf'>syncml:auth-md5</Type></Meta><Data>kHzMn3RWFGWSKeBpXicppQ==</Data>",
|
|
|
|
"<Type xmlns='syncml:metinf'>syncml:auth-basic</Type></Meta><Data>dGVzdDp0ZXN0</Data>")
|
|
|
|
connection.Process(plain_auth, 'application/vnd.syncml+xml')
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " got reply"])
|
|
|
|
DBusUtil.quit_events = []
|
|
|
|
self.failIfEqual(DBusUtil.reply, None)
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
|
2009-11-11 10:55:15 +01:00
|
|
|
# credentials should have been accepted because with basic auth,
|
|
|
|
# credentials can be replayed; 508 = "refresh required" is normal
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnless('<Status><CmdID>2</CmdID><MsgRef>1</MsgRef><CmdRef>1</CmdRef><Cmd>Alert</Cmd><TargetRef>addressbook</TargetRef><SourceRef>./addressbook</SourceRef><Data>508</Data>' in DBusUtil.reply[0])
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[3], False)
|
|
|
|
self.failIfEqual(DBusUtil.reply[4], '')
|
2009-11-05 18:04:41 +01:00
|
|
|
connection.Close(False, 'good bye')
|
|
|
|
loop.run()
|
2009-11-09 21:10:10 +01:00
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " aborted",
|
|
|
|
"session done"])
|
2009-11-09 21:10:10 +01:00
|
|
|
|
|
|
|
def testStartSyncTwice(self):
|
|
|
|
"""send the same SyncML message twice, starting two sessions"""
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setupConfig()
|
2009-11-09 21:10:10 +01:00
|
|
|
conpath, connection = self.getConnection()
|
|
|
|
connection.Process(TestConnection.message1, 'application/vnd.syncml+xml')
|
|
|
|
loop.run()
|
|
|
|
# TODO: check events
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " got reply"])
|
|
|
|
self.failIfEqual(DBusUtil.reply, None)
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[3], False)
|
|
|
|
self.failIfEqual(DBusUtil.reply[4], '')
|
|
|
|
DBusUtil.reply = None
|
|
|
|
DBusUtil.quit_events = []
|
2009-11-09 21:10:10 +01:00
|
|
|
|
|
|
|
# Now start another session with the same client *without*
|
|
|
|
# closing the first one. The server should detect this
|
|
|
|
# and forcefully close the first one.
|
|
|
|
conpath2, connection2 = self.getConnection()
|
|
|
|
connection2.Process(TestConnection.message1, 'application/vnd.syncml+xml')
|
|
|
|
|
|
|
|
# reasons for leaving the loop, in random order:
|
|
|
|
# - abort of first connection
|
|
|
|
# - first session done
|
|
|
|
# - reply for second one
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
DBusUtil.quit_events.sort()
|
2009-11-09 21:10:10 +01:00
|
|
|
expected = [ "connection " + conpath + " aborted",
|
|
|
|
"session done",
|
|
|
|
"connection " + conpath2 + " got reply" ]
|
|
|
|
expected.sort()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, expected)
|
|
|
|
self.failIfEqual(DBusUtil.reply, None)
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[3], False)
|
|
|
|
self.failIfEqual(DBusUtil.reply[4], '')
|
|
|
|
DBusUtil.quit_events = []
|
2009-11-09 21:10:10 +01:00
|
|
|
|
|
|
|
# now quit for good
|
|
|
|
connection2.Close(False, 'good bye')
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath2 + " aborted",
|
|
|
|
"session done"])
|
2009-11-09 21:10:10 +01:00
|
|
|
|
|
|
|
def testKillInactive(self):
|
|
|
|
"""block server with client A, then let client B connect twice"""
|
2010-01-21 09:15:19 +01:00
|
|
|
#set up 2 configs
|
|
|
|
self.setupConfig()
|
|
|
|
self.setupConfig("dummy", "sc-pim-ppc")
|
2009-11-09 21:10:10 +01:00
|
|
|
conpath, connection = self.getConnection()
|
|
|
|
connection.Process(TestConnection.message1, 'application/vnd.syncml+xml')
|
|
|
|
loop.run()
|
|
|
|
# TODO: check events
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " got reply"])
|
|
|
|
self.failIfEqual(DBusUtil.reply, None)
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[3], False)
|
|
|
|
self.failIfEqual(DBusUtil.reply[4], '')
|
|
|
|
DBusUtil.reply = None
|
|
|
|
DBusUtil.quit_events = []
|
2009-11-09 21:10:10 +01:00
|
|
|
|
|
|
|
# Now start two more sessions with the second client *without*
|
|
|
|
# closing the first one. The server should remove only the
|
|
|
|
# first connection of client B.
|
|
|
|
message1_clientB = TestConnection.message1.replace("sc-api-nat", "sc-pim-ppc")
|
|
|
|
conpath2, connection2 = self.getConnection()
|
|
|
|
connection2.Process(message1_clientB, 'application/vnd.syncml+xml')
|
|
|
|
conpath3, connection3 = self.getConnection()
|
|
|
|
connection3.Process(message1_clientB, 'application/vnd.syncml+xml')
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, [ "connection " + conpath2 + " aborted" ])
|
|
|
|
DBusUtil.quit_events = []
|
2009-11-09 21:10:10 +01:00
|
|
|
|
|
|
|
# now quit for good
|
|
|
|
connection3.Close(False, 'good bye client B')
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, [ "connection " + conpath3 + " aborted" ])
|
|
|
|
DBusUtil.quit_events = []
|
2009-11-09 21:10:10 +01:00
|
|
|
connection.Close(False, 'good bye client A')
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
2010-01-14 12:09:33 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " aborted",
|
|
|
|
"session done"])
|
|
|
|
|
|
|
|
@timeout(20)
|
|
|
|
def testTimeoutSync(self):
|
|
|
|
"""start a sync, then wait for server to detect that we stopped replying
|
|
|
|
|
|
|
|
The server-side configuration for sc-api-nat must contain a retryDuration=10
|
|
|
|
because this test itself will time out with a failure after 20 seconds."""
|
2010-01-21 09:15:19 +01:00
|
|
|
self.setupConfig()
|
2010-01-14 12:09:33 +01:00
|
|
|
conpath, connection = self.getConnection()
|
|
|
|
connection.Process(TestConnection.message1, 'application/vnd.syncml+xml')
|
|
|
|
loop.run()
|
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " got reply"])
|
|
|
|
DBusUtil.quit_events = []
|
|
|
|
# TODO: check events
|
|
|
|
self.failIfEqual(DBusUtil.reply, None)
|
|
|
|
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
|
|
|
|
# wait for connection reset and "session done" due to timeout
|
|
|
|
loop.run()
|
|
|
|
loop.run()
|
2009-11-18 22:49:48 +01:00
|
|
|
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " aborted",
|
|
|
|
"session done"])
|
2009-10-14 17:59:03 +02:00
|
|
|
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
class TestMultipleConfigs(unittest.TestCase, DBusUtil):
|
2009-11-20 16:45:11 +01:00
|
|
|
""" sharing of properties between configs
|
|
|
|
|
|
|
|
Creates and tests the configs 'foo', 'bar', 'foo@other_context',
|
|
|
|
'@default' and checks that 'defaultPeer' (global), 'syncURL' (per
|
|
|
|
peer), 'evolutionsource' (per source), 'uri' (per source and peer)
|
|
|
|
are shared correctly.
|
|
|
|
|
|
|
|
Runs with a the server ready, without session."""
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.setUpServer()
|
|
|
|
|
|
|
|
def run(self, result):
|
|
|
|
self.runTest(result)
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def setupEmpty(self):
|
|
|
|
"""Creates empty configs 'foo', 'bar', 'foo@other_context'.
|
|
|
|
Updating non-existant configs is an error. Use this
|
|
|
|
function before trying to update one of these configs."""
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
self.setUpSession("foo")
|
|
|
|
self.session.SetConfig(False, False, {"" : {}})
|
|
|
|
self.session.Detach()
|
|
|
|
self.setUpSession("bar")
|
|
|
|
self.session.SetConfig(False, False, {"": {}})
|
|
|
|
self.session.Detach()
|
|
|
|
self.setUpSession("foo@other_CONTEXT")
|
|
|
|
self.session.SetConfig(False, False, {"": {}})
|
|
|
|
self.session.Detach()
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def setupConfigs(self):
|
|
|
|
"""Creates polulated configs 'foo', 'bar', 'foo@other_context'."""
|
|
|
|
self.setupEmpty()
|
|
|
|
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
# update normal view on "foo"
|
|
|
|
self.setUpSession("foo")
|
|
|
|
self.session.SetConfig(True, False,
|
|
|
|
{ "" : { "defaultPeer" : "foobar_peer",
|
2009-11-21 18:17:17 +01:00
|
|
|
"deviceId" : "shared-device-identifier",
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"syncURL": "http://scheduleworld" },
|
2009-11-20 13:23:15 +01:00
|
|
|
"source/calendar" : { "uri" : "cal3" },
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"source/addressbook" : { "evolutionsource": "Personal",
|
2009-11-20 13:23:15 +01:00
|
|
|
"sync" : "two-way",
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"uri": "card3" } },
|
|
|
|
utf8_strings=True)
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# "bar" shares properties with "foo"
|
|
|
|
self.setUpSession("bar")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
2009-11-21 18:17:17 +01:00
|
|
|
self.failUnlessEqual(config[""]["deviceId"], "shared-device-identifier")
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Personal")
|
|
|
|
self.session.SetConfig(True, False,
|
|
|
|
{ "" : { "syncURL": "http://funambol" },
|
2009-11-20 13:23:15 +01:00
|
|
|
"source/calendar" : { "uri" : "cal" },
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"source/addressbook" : { "evolutionsource": "Work",
|
2009-11-20 13:23:15 +01:00
|
|
|
"sync" : "refresh-from-client",
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
"uri": "card" } },
|
|
|
|
utf8_strings=True)
|
|
|
|
self.session.Detach()
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def testSharing(self):
|
|
|
|
"""set up configs and tests reading them"""
|
|
|
|
self.setupConfigs()
|
|
|
|
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
# check how view "foo" has been modified
|
|
|
|
self.setUpSession("Foo@deFAULT")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnlessEqual(config[""]["syncURL"], "http://scheduleworld")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Work")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["uri"], "card3")
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# different ways of addressing this context
|
|
|
|
self.setUpSession("")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnless("source/addressbook" in config)
|
|
|
|
self.failIf("uri" in config["source/addressbook"])
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
self.setUpSession("@DEFAULT")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
2009-11-21 18:17:17 +01:00
|
|
|
self.failUnlessEqual(config[""]["deviceId"], "shared-device-identifier")
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
self.failUnless("source/addressbook" in config)
|
|
|
|
self.failIf("uri" in config["source/addressbook"])
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# different context
|
|
|
|
self.setUpSession("@other_context")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failIf("source/addressbook" in config)
|
|
|
|
self.session.Detach()
|
|
|
|
|
2009-12-01 18:03:39 +01:00
|
|
|
def testSharedTemplate(self):
|
|
|
|
"""templates must contain shared properties"""
|
|
|
|
self.setupConfigs()
|
|
|
|
|
|
|
|
config = self.server.GetConfig("scheduleworld", True, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnlessEqual(config[""]["deviceId"], "shared-device-identifier")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Work")
|
|
|
|
|
2010-03-02 16:30:10 +01:00
|
|
|
def testSharedType(self):
|
|
|
|
"""'type' must be set per-peer and shared"""
|
|
|
|
self.setupConfigs()
|
|
|
|
|
|
|
|
# writing for peer modifies "type" in "foo" and context
|
|
|
|
self.setUpSession("Foo@deFAULT")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
config["source/addressbook"]["type"] = "file:text/vcard:3.0"
|
|
|
|
self.session.SetConfig(True, False,
|
|
|
|
config,
|
|
|
|
utf8_strings=True)
|
|
|
|
config = self.server.GetConfig("Foo", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
|
|
|
|
config = self.server.GetConfig("@default", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# writing in context only changes the context
|
|
|
|
self.setUpSession("@deFAULT")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
config["source/addressbook"]["type"] = "file:text/x-vcard:2.1"
|
|
|
|
self.session.SetConfig(True, False,
|
|
|
|
config,
|
|
|
|
utf8_strings=True)
|
|
|
|
config = self.server.GetConfig("Foo", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
|
|
|
|
config = self.server.GetConfig("@default", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/x-vcard:2.1")
|
|
|
|
|
2010-03-30 17:39:36 +02:00
|
|
|
def testSharedTypeOther(self):
|
|
|
|
"""'type' must not be overwritten when set in the context"""
|
|
|
|
# writing for peer modifies "type" in "foo" and context "@other"
|
|
|
|
self.setUpSession("Foo@other")
|
|
|
|
config = self.server.GetConfig("ScheduleWorld@other", True, utf8_strings=True)
|
|
|
|
config["source/addressbook"]["type"] = "file:text/vcard:3.0"
|
|
|
|
self.session.SetConfig(False, False,
|
|
|
|
config,
|
|
|
|
utf8_strings=True)
|
|
|
|
config = self.server.GetConfig("Foo", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
|
|
|
|
config = self.server.GetConfig("@other", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# adding second client must preserve type
|
|
|
|
self.setUpSession("bar@other")
|
|
|
|
config = self.server.GetConfig("Funambol@other", True, utf8_strings=True)
|
2010-04-07 05:27:42 +02:00
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "addressbook")
|
2010-03-30 17:39:36 +02:00
|
|
|
self.session.SetConfig(False, False,
|
|
|
|
config,
|
|
|
|
utf8_strings=True)
|
|
|
|
config = self.server.GetConfig("bar", False, utf8_strings=True)
|
2010-04-07 05:27:42 +02:00
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "addressbook")
|
2010-03-30 17:39:36 +02:00
|
|
|
config = self.server.GetConfig("@other", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def testOtherContext(self):
|
|
|
|
"""write into independent context"""
|
|
|
|
self.setupConfigs()
|
|
|
|
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
# write independent "foo@other_context" config
|
|
|
|
self.setUpSession("foo@other_context")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
config[""]["syncURL"] = "http://scheduleworld2"
|
|
|
|
config["source/addressbook"] = { "evolutionsource": "Play",
|
|
|
|
"uri": "card30" }
|
|
|
|
self.session.SetConfig(True, False,
|
|
|
|
config,
|
|
|
|
utf8_strings=True)
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnlessEqual(config[""]["syncURL"], "http://scheduleworld2")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Play")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["uri"], "card30")
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# "foo" modified?
|
|
|
|
self.setUpSession("foo")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnlessEqual(config[""]["syncURL"], "http://scheduleworld")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Work")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["uri"], "card3")
|
|
|
|
self.session.Detach()
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def testSourceRemovalLocal(self):
|
|
|
|
'''remove "addressbook" source in "foo"'''
|
|
|
|
self.setupConfigs()
|
2009-11-20 13:23:15 +01:00
|
|
|
self.setUpSession("foo")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
del config["source/addressbook"]
|
|
|
|
self.session.SetConfig(False, False, config, utf8_strings=True)
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# "addressbook" still exists in "foo" but only with default values
|
|
|
|
config = self.server.GetConfig("foo", False, utf8_strings=True)
|
|
|
|
self.failIf("uri" in config["source/addressbook"])
|
|
|
|
self.failIf("sync" in config["source/addressbook"])
|
|
|
|
|
|
|
|
# "addressbook" unchanged in "bar"
|
|
|
|
config = self.server.GetConfig("bar", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["uri"], "card")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["sync"], "refresh-from-client")
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def testSourceRemovalGlobal(self):
|
|
|
|
'''remove "addressbook" everywhere'''
|
|
|
|
self.setupConfigs()
|
2009-11-20 13:23:15 +01:00
|
|
|
self.setUpSession("")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
del config["source/addressbook"]
|
|
|
|
self.session.SetConfig(False, False, config, utf8_strings=True)
|
|
|
|
self.session.Detach()
|
|
|
|
|
|
|
|
# "addressbook" gone in "foo" and "bar"
|
|
|
|
config = self.server.GetConfig("foo", False, utf8_strings=True)
|
|
|
|
self.failIf("source/addressbook" in config)
|
|
|
|
config = self.server.GetConfig("bar", False, utf8_strings=True)
|
|
|
|
self.failIf("source/addressbook" in config)
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def testRemovePeer(self):
|
|
|
|
'''check listing of peers while removing "bar"'''
|
|
|
|
self.setupConfigs()
|
|
|
|
self.testOtherContext()
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
self.setUpSession("bar")
|
|
|
|
peers = self.session.GetConfigs(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(peers,
|
|
|
|
[ "bar", "foo", "foo@other_context" ])
|
|
|
|
peers2 = self.server.GetConfigs(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(peers, peers2)
|
2009-11-20 13:23:15 +01:00
|
|
|
# remove "bar"
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
self.session.SetConfig(False, False, {}, utf8_strings=True)
|
|
|
|
peers = self.server.GetConfigs(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(peers,
|
|
|
|
[ "foo", "foo@other_context" ])
|
|
|
|
self.session.Detach()
|
2009-11-20 13:23:15 +01:00
|
|
|
|
|
|
|
# other configs should not have been affected
|
|
|
|
config = self.server.GetConfig("foo", False, utf8_strings=True)
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnlessEqual(config[""]["syncURL"], "http://scheduleworld")
|
2009-11-20 13:23:15 +01:00
|
|
|
self.failUnlessEqual(config["source/calendar"]["uri"], "cal3")
|
|
|
|
config = self.server.GetConfig("foo@other_context", False, utf8_strings=True)
|
config: share properties between peers, configuration view without peer
This patch makes the configuration layout with per-source and per-peer
properties the default for new configurations. Migrating old
configurations is not implemented. The command line has not
been updated at all (MB #8048). The D-Bus API is fairly complete,
only listing sessions independently of a peer is missing (MB #8049).
The key concept of this patch is that a pseudo-node implemented by
MultiplexConfigNode provides a view on all user-visible or hidden
properties. Based on the property name, it looks up the property
definition, picks one of the underlying nodes based on the property
visibility and sharing attributes, then reads and writes the property
via that node. Clearing properties is not needed and not implemented,
because of the uncertain semantic (really remove shared properties?!).
The "sync" property must be available both in the per-source config
(to pick a backend independently of a specific peer) and in the
per-peer configuration (to select a specific data format). This is
solved by making the property special (SHARED_AND_UNSHARED flag) and
then writing it into two nodes. Reading is done from the more specific
per-peer node, with the other node acting as fallback.
The MultiplexConfigNode has to implement the FilterConfigNode API
because it is used as one by the code which sets passwords in the
filter. For this to work, the base FilterConfigNode implementation must
use virtual method calls.
The TestDBusSessionConfig.testUpdateConfigError checks that the error
generated for an incorrect "sync" property contains the path of the
config.ini file. The meaning of the error message in this case is that
the wrong value is *for* that file, not that the property is already
wrong *in* the file, but that's okay.
The MultiplexConfigNode::getName() can only return a fixed name. To
satisfy the test and because it is the right choice at the moment for
all properties which might trigger such an error, it now is configured
so that it returns the most specific path of the non-shared
properties.
"syncevolution --print-config" shows errors that are in files. Wrong
command line parameters are rejected with a message that refers to the
command line parameter ("--source-property sync=foo").
A future enhancement would be to make the name depend on the
property (MB#8037).
Because an empty string is now a valid configuration name (referencing
the source properties without the per-peer properties) several checks
for such empty strings were removed. The corresponding tests were
updated resp. removed. Instead of talking about "server not found",
the more neutral name "configuration" is used. The new
TestMultipleConfigs.testSharing() covers the semantic of sharing
properties between multiple configs.
Access to non-existant nodes is routed into the new
DevNullConfigNode. It always returns an empty string when reading and
throws an error when trying to write into it. Unintentionally writing
into a config.ini file therefore became harder, compared with the
previous instantiation of SyncContext() with empty config name.
The parsing of incoming messages uses a SyncContext which is bound to
a VolatileConfigNode. This allows reading and writing of properties
without any risk of touching files on disk.
The patch which introduced the new config nodes was not complete yet
with regards to the new layout. Removing nodes and trees used the
wrong root path: getRootPath() refers to the most specific peer
config, m_root to the part without the peer path. SyncConfig must
distinguish between a view with peer-specific properties and one
without, which is done by setting the m_peerPath only if a peer was
selected. Copying properties must know whether writing per-specific
properties ("unshared") is wanted, because trying to do it for a view
without those properties would trigger the DevNullConfigNode
exception.
SyncConfig::removeSyncSource() removes source properties both in the
shared part of the config and in *all* peers. This is used by
Session.SetConfig() for the case that the caller is a) setting instead
of updating the config and b) not providing any properties for the
source. This is clearly a risky operation which should not be done
when there are other peers which still use the source. We might have a
problem in our D-Bus API definition for "removing a peer
configuration" (MB #8059) because it always has an effect on other
peers.
The property registries were initialized implicitly before. With the
recent changes it happened that SyncContext was initialized to analyze
a SyncML message without initializing the registry, which caused
getRemoteDevID() to use a property where the hidden flag had not been
set yet.
Moving all of these additional flags into the property constructors is
awkward (which is why they are in the getRegistry() methods), so this
was fixed by initializing the properties in the SyncConfig
constructors by asking for the registries. Because there is no way to
access them except via the registry and SyncConfig instances (*), this
should ensure that the properties are valid when used.
(*) Exception are some properties which are declared publicly to have access
to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnlessEqual(config[""]["syncURL"], "http://scheduleworld2")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Play")
|
|
|
|
self.failUnlessEqual(config["source/addressbook"]["uri"], "card30")
|
|
|
|
|
2009-11-20 16:45:11 +01:00
|
|
|
def testRemoveContext(self):
|
|
|
|
'''remove complete config'''
|
|
|
|
self.setupConfigs()
|
2009-11-20 13:23:15 +01:00
|
|
|
self.setUpSession("")
|
|
|
|
self.session.SetConfig(False, False, {}, utf8_strings=True)
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
peers = self.server.GetConfigs(False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(peers, ['foo@other_context'])
|
|
|
|
self.session.Detach()
|
|
|
|
|
2009-11-21 18:25:19 +01:00
|
|
|
def testTemplates(self):
|
|
|
|
'''templates reuse common properties'''
|
|
|
|
self.setupConfigs()
|
|
|
|
|
|
|
|
# deviceID must be shared and thus be reused in templates
|
|
|
|
self.setUpSession("")
|
|
|
|
config = self.session.GetConfig(False, utf8_strings=True)
|
|
|
|
config[""]["DEVICEID"] = "shared-device-identifier"
|
|
|
|
self.session.SetConfig(True, False, config, utf8_strings=True)
|
|
|
|
config = self.server.GetConfig("", False, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["deviceId"], "shared-device-identifier")
|
|
|
|
|
|
|
|
# get template for default context
|
|
|
|
config = self.server.GetConfig("scheduleworld", True, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failUnlessEqual(config[""]["deviceId"], "shared-device-identifier")
|
|
|
|
|
|
|
|
# now for @other_context - different device ID!
|
|
|
|
config = self.server.GetConfig("scheduleworld@other_context", True, utf8_strings=True)
|
|
|
|
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
|
|
|
|
self.failIfEqual(config[""]["deviceId"], "shared-device-identifier")
|
|
|
|
|
2009-10-13 16:12:34 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
unittest.main()
|