ProPeler
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
cpanel-ccs
/
contrib
/
performance
/
loadtest
/
Filename :
test_profiles.py
back
Copy
## # Copyright (c) 2011-2017 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ## """ Tests for loadtest.profiles. """ from StringIO import StringIO from caldavclientlibrary.protocol.caldav.definitions import caldavxml, csxml from twisted.trial.unittest import TestCase from twisted.internet.task import Clock from twisted.internet.defer import succeed, fail from twisted.web.http import NO_CONTENT, PRECONDITION_FAILED from twisted.web.client import Response from twistedcaldav.ical import Component, Property from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter, OperationLogger, AlarmAcknowledger, AttachmentDownloader from contrib.performance.loadtest.population import Populator, CalendarClientSimulator from contrib.performance.loadtest.ical import IncorrectResponseCode, Calendar, Event, BaseClient from contrib.performance.loadtest.sim import _DirectoryRecord import os SIMPLE_EVENT = """\ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//iCal 4.0.3//EN CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:America/New_York BEGIN:DAYLIGHT TZOFFSETFROM:-0500 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU DTSTART:20070311T020000 TZNAME:EDT TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:-0400 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU DTSTART:20071104T020000 TZNAME:EST TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20101018T155431Z UID:C98AD237-55AD-4F7D-9009-0D355D835822 DTEND;TZID=America/New_York:20101021T130000 TRANSP:OPAQUE SUMMARY:Simple event DTSTART;TZID=America/New_York:20101021T120000 DTSTAMP:20101018T155438Z SEQUENCE:2 END:VEVENT END:VCALENDAR """ INVITED_EVENT = """\ BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTIMEZONE TZID:America/New_York BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT UID:882C3D50-0DAE-45CB-A2E7-DA75DA9BE452 DTSTART;TZID=America/New_York:20110131T130000 DTEND;TZID=America/New_York:20110131T140000 ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC CEPTED:urn:uuid:user01 ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02@example.com;PARTSTAT=NE EDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:urn:uuid:user02 CREATED:20110124T170357Z DTSTAMP:20110124T170425Z ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01 SEQUENCE:3 SUMMARY:Some Event For You TRANSP:TRANSPARENT X-APPLE-NEEDS-REPLY:TRUE END:VEVENT END:VCALENDAR """ ACCEPTED_EVENT = """\ BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTIMEZONE TZID:America/New_York BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT UID:882C3D50-0DAE-45CB-A2E7-DA75DA9BE452 DTSTART;TZID=America/New_York:20110131T130000 DTEND;TZID=America/New_York:20110131T140000 ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC CEPTED:urn:uuid:user01 ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02@example.com;PARTSTAT=AC CEPTED:urn:uuid:user02 CREATED:20110124T170357Z DTSTAMP:20110124T170425Z ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01 SEQUENCE:3 SUMMARY:Some Event For You TRANSP:TRANSPARENT X-APPLE-NEEDS-REPLY:TRUE END:VEVENT END:VCALENDAR """ INBOX_REPLY = """\ BEGIN:VCALENDAR METHOD:REPLY VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 ORGANIZER;CN="User 01":mailto:user1@example.com ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1@example.com END:VEVENT END:VCALENDAR """ class AnyUser(object): def __getitem__(self, index): return _AnyRecord(index) class _AnyRecord(object): def __init__(self, index): self.uid = u"user%02d" % (index,) self.password = u"user%02d" % (index,) self.commonName = u"User %02d" % (index,) self.email = u"user%02d@example.com" % (index,) self.guid = u"user%02d" % (index,) class Deterministic(object): def __init__(self, value=None): self.value = value def gauss(self, mean, stddev): """ Pretend to return a value from a gaussian distribution with mu parameter C{mean} and sigma parameter C{stddev}. But actually always return C{mean + 1}. """ return mean + 1 def choice(self, sequence): return sequence[0] def sample(self): return self.value class StubClient(BaseClient): """ Stand in for an iCalendar client. @ivar rescheduled: A set of event URLs which will not allow attendee changes due to a changed schedule tag. @ivar _pendingFailures: dict mapping URLs to failure objects """ def __init__(self, number, serializePath): self.serializePath = serializePath os.mkdir(self.serializePath) self.title = "StubClient" self._events = {} self._calendars = {} self._pendingFailures = {} self.record = _DirectoryRecord( "user%02d" % (number,), "user%02d" % (number,), "User %02d" % (number,), "user%02d@example.org" % (number,), "user%02d" % (number,)) self.email = "mailto:user%02d@example.com" % (number,) self.uuid = "urn:uuid:user%02d" % (number,) self.rescheduled = set() self.started = True def _failDeleteWithObject(self, href, failureObject): """ Accessor for inserting intentional failures for deletes. """ self._pendingFailures[href] = failureObject def serializeLocation(self): """ Return the path to the directory where data for this user is serialized. """ if self.serializePath is None or not os.path.isdir(self.serializePath): return None key = "%s-%s" % (self.record.uid, "StubClient") path = os.path.join(self.serializePath, key) if not os.path.exists(path): os.mkdir(path) elif not os.path.isdir(path): return None return path def addEvent(self, href, vevent, attachmentSize=0): self._events[href] = Event(self.serializePath, href, None, vevent) return succeed(None) def addInvite(self, href, vevent, attachmentSize=0, lookupPercentage=0): return self.addEvent(href, vevent, attachmentSize=attachmentSize) def deleteEvent(self, href,): del self._events[href] calendar, uid = href.rsplit('/', 1) del self._calendars[calendar + '/'].events[uid] if href in self._pendingFailures: failureObject = self._pendingFailures.pop(href) return fail(failureObject) else: return succeed(None) def updateEvent(self, href): self.rescheduled.remove(href) return succeed(None) def addEventAttendee(self, href, attendee): vevent = self._events[href].component vevent.mainComponent().addProperty(attendee) self._events[href].component = vevent def changeEventAttendee(self, href, old, new): if href in self.rescheduled: return fail( IncorrectResponseCode( NO_CONTENT, Response( ('HTTP', 1, 1), PRECONDITION_FAILED, 'Precondition Failed', None, None), None ), ) vevent = self._events[href].component vevent.mainComponent().removeProperty(old) vevent.mainComponent().addProperty(new) self._events[href].component = vevent return succeed(None) def _makeSelfAttendee(self): attendee = Property( name=u'ATTENDEE', value=self.email, params={ 'CN': self.record.commonName, 'CUTYPE': 'INDIVIDUAL', 'PARTSTAT': 'ACCEPTED', }, ) return attendee def _makeSelfOrganizer(self): organizer = Property( name=u'ORGANIZER', value=self.email, params={ 'CN': self.record.commonName, }, ) return organizer class SequentialDistribution(object): def __init__(self, values): self.values = values def sample(self): return self.values.pop(0) # These were for the old Inviter class, before RealisticInviter was renamed # to Inviter. Keeping them around in case there are some we want to copy # over to the new Inviter: # # class InviterTests(TestCase): # """ # Tests for loadtest.profiles.Inviter. # """ # def setUp(self): # self.sim = CalendarClientSimulator( # AnyUser(), Populator(None), None, None, None, None, None, None) # def _simpleAccount(self, userNumber, eventText): # client = StubClient(userNumber, self.mktemp()) # vevent = Component.fromString(eventText) # calendar = Calendar( # caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/cal/', None) # client._calendars.update({calendar.url: calendar}) # event = Event(client.serializeLocation(), calendar.url + u'1234.ics', None, vevent) # client._events.update({event.url: event}) # calendar.events = {u'1234.ics': event} # return vevent, event, calendar, client # def test_enabled(self): # userNumber = 13 # client = StubClient(userNumber, self.mktemp()) # inviter = Inviter(None, self.sim, client, userNumber, **{"enabled": False}) # self.assertEqual(inviter.enabled, False) # inviter = Inviter(None, self.sim, client, userNumber, **{"enabled": True}) # self.assertEqual(inviter.enabled, True) # def test_doNotAddAttendeeToInbox(self): # """ # When the only calendar with any events is a schedule inbox, no # attempt is made to add attendees to an event on that calendar. # """ # userNumber = 10 # vevent, _ignore_event, calendar, client = self._simpleAccount( # userNumber, SIMPLE_EVENT) # calendar.resourceType = caldavxml.schedule_inbox # inviter = Inviter(None, self.sim, client, userNumber) # inviter._invite() # self.assertFalse(vevent.mainComponent().hasProperty('ATTENDEE')) # def test_doNotAddAttendeeToNoCalendars(self): # """ # When there are no calendars and no events at all, the inviter # does nothing. # """ # userNumber = 13 # client = StubClient(userNumber, self.mktemp()) # inviter = Inviter(None, self.sim, client, userNumber) # inviter._invite() # self.assertEquals(client._events, {}) # self.assertEquals(client._calendars, {}) # def test_doNotAddAttendeeToUninitializedEvent(self): # """ # When there is an L{Event} on a calendar but the details of the # event have not yet been retrieved, no attempt is made to add # invitees to that event. # """ # userNumber = 19 # _ignore_vevent, event, calendar, client = self._simpleAccount( # userNumber, SIMPLE_EVENT) # event.component = event.etag = event.scheduleTag = None # inviter = Inviter(None, self.sim, client, userNumber) # inviter._invite() # self.assertEquals(client._events, {event.url: event}) # self.assertEquals(client._calendars, {calendar.url: calendar}) # def test_addAttendeeToEvent(self): # """ # When there is a normal calendar with an event, inviter adds an # attendee to it. # """ # userNumber = 16 # _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount( # userNumber, SIMPLE_EVENT) # inviter = Inviter(Clock(), self.sim, client, userNumber) # inviter.setParameters(inviteeDistribution=Deterministic(1)) # inviter._invite() # attendees = tuple(event.component.mainComponent().properties('ATTENDEE')) # self.assertEquals(len(attendees), 1) # for paramname, paramvalue in { # 'CN': 'User %d' % (userNumber + 1,), # 'CUTYPE': 'INDIVIDUAL', # 'PARTSTAT': 'NEEDS-ACTION', # 'ROLE': 'REQ-PARTICIPANT', # 'RSVP': 'TRUE' # }.items(): # self.assertTrue(attendees[0].hasParameter(paramname)) # self.assertEqual(attendees[0].parameterValue(paramname), paramvalue) # def test_doNotAddSelfToEvent(self): # """ # If the inviter randomly selects its own user to be added to # the attendee list, a different user is added instead. # """ # selfNumber = 12 # _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount( # selfNumber, SIMPLE_EVENT) # otherNumber = 20 # values = [selfNumber - selfNumber, otherNumber - selfNumber] # inviter = Inviter(Clock(), self.sim, client, selfNumber) # inviter.setParameters(inviteeDistribution=SequentialDistribution(values)) # inviter._invite() # attendees = tuple(event.component.mainComponent().properties('ATTENDEE')) # self.assertEquals(len(attendees), 1) # for paramname, paramvalue in { # 'CN': 'User %d' % (otherNumber,), # 'CUTYPE': 'INDIVIDUAL', # 'PARTSTAT': 'NEEDS-ACTION', # 'ROLE': 'REQ-PARTICIPANT', # 'RSVP': 'TRUE' # }.items(): # self.assertTrue(attendees[0].hasParameter(paramname)) # self.assertEqual(attendees[0].parameterValue(paramname), paramvalue) # def test_doNotAddExistingToEvent(self): # """ # If the inviter randomly selects a user which is already an # invitee on the event, a different user is added instead. # """ # selfNumber = 1 # _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount( # selfNumber, INVITED_EVENT) # invitee = tuple(event.component.mainComponent().properties('ATTENDEE'))[0] # inviteeNumber = int(invitee.parameterValue('CN').split()[1]) # anotherNumber = inviteeNumber + 5 # values = [inviteeNumber - selfNumber, anotherNumber - selfNumber] # inviter = Inviter(Clock(), self.sim, client, selfNumber) # inviter.setParameters(inviteeDistribution=SequentialDistribution(values)) # inviter._invite() # attendees = tuple(event.component.mainComponent().properties('ATTENDEE')) # self.assertEquals(len(attendees), 3) # for paramname, paramvalue in { # 'CN': 'User %02d' % (anotherNumber,), # 'CUTYPE': 'INDIVIDUAL', # 'PARTSTAT': 'NEEDS-ACTION', # 'ROLE': 'REQ-PARTICIPANT', # 'RSVP': 'TRUE' # }.items(): # self.assertTrue(attendees[2].hasParameter(paramname)) # self.assertEqual(attendees[2].parameterValue(paramname), paramvalue) # def test_everybodyInvitedAlready(self): # """ # If the first so-many randomly selected users we come across # are already attendees on the event, the invitation attempt is # abandoned. # """ # selfNumber = 1 # vevent, _ignore_event, _ignore_calendar, client = self._simpleAccount( # selfNumber, INVITED_EVENT) # inviter = Inviter(Clock(), self.sim, client, selfNumber) # # Always return a user number which has already been invited. # inviter.setParameters(inviteeDistribution=Deterministic(2 - selfNumber)) # inviter._invite() # attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) # self.assertEquals(len(attendees), 2) # def test_doNotInviteToSomeoneElsesEvent(self): # """ # If there are events on our calendar which are being organized # by someone else, the inviter does not attempt to invite new # users to them. # """ # selfNumber = 2 # vevent, _ignore_event, _ignore_calendar, client = self._simpleAccount( # selfNumber, INVITED_EVENT) # inviter = Inviter(None, self.sim, client, selfNumber) # # Try to send an invitation, but with only one event on the # # calendar, of which we are not the organizer. It should be # # unchanged afterwards. # inviter._invite() # attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) # self.assertEqual(len(attendees), 2) # self.assertEqual(attendees[0].parameterValue('CN'), 'User 01') # self.assertEqual(attendees[1].parameterValue('CN'), 'User 02') class InviterTests(TestCase): """ Tests for loadtest.profiles.Inviter. """ def setUp(self): self.sim = CalendarClientSimulator( AnyUser(), Populator(None), None, None, None, None, None, None) def _simpleAccount(self, userNumber, eventText): client = StubClient(userNumber, self.mktemp()) vevent = Component.fromString(eventText) calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/cal/', None) event = Event(client.serializeLocation(), calendar.url + u'1234.ics', None, vevent) calendar.events = {u'1234.ics': event} client._events.update({event.url: event}) client._calendars.update({calendar.url: calendar}) return vevent, event, calendar, client def test_enabled(self): userNumber = 13 client = StubClient(userNumber, self.mktemp()) inviter = Inviter(None, self.sim, client, userNumber, **{"enabled": False}) self.assertEqual(inviter.enabled, False) inviter = Inviter(None, self.sim, client, userNumber, **{"enabled": True}) self.assertEqual(inviter.enabled, True) def test_doNotAddInviteToInbox(self): """ When the only calendar with any events is a schedule inbox, no attempt is made to add attendees to that calendar. """ calendar = Calendar( caldavxml.schedule_inbox, set(), u'inbox', u'/sched/inbox', None) userNumber = 13 client = StubClient(userNumber, self.mktemp()) client._calendars.update({calendar.url: calendar}) inviter = Inviter(None, self.sim, client, userNumber, **{"enabled": False}) inviter._invite() self.assertEquals(client._events, {}) def test_doNotAddInviteToNoCalendars(self): """ When there are no calendars and no events at all, the inviter does nothing. """ userNumber = 13 client = StubClient(userNumber, self.mktemp()) inviter = Inviter(None, self.sim, client, userNumber) inviter._invite() self.assertEquals(client._events, {}) self.assertEquals(client._calendars, {}) def test_addInvite(self): """ When there is a normal calendar, inviter adds an invite to it. """ calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None) userNumber = 16 serializePath = self.mktemp() os.mkdir(serializePath) client = StubClient(userNumber, self.mktemp()) client._calendars.update({calendar.url: calendar}) inviter = Inviter(Clock(), self.sim, client, userNumber) inviter.setParameters( inviteeDistribution=Deterministic(1), inviteeCountDistribution=Deterministic(1) ) inviter._invite() self.assertEquals(len(client._events), 1) attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE')) expected = set(("mailto:user%02d@example.com" % (userNumber,), "mailto:user%02d@example.com" % (userNumber + 1,),)) for attendee in attendees: expected.remove(attendee.value()) self.assertEqual(len(expected), 0) def test_doNotAddSelfToEvent(self): """ If the inviter randomly selects its own user to be added to the attendee list, a different user is added instead. """ calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None) selfNumber = 12 client = StubClient(selfNumber, self.mktemp()) client._calendars.update({calendar.url: calendar}) otherNumber = 20 values = [selfNumber - selfNumber, otherNumber - selfNumber] inviter = Inviter(Clock(), self.sim, client, selfNumber) inviter.setParameters( inviteeDistribution=SequentialDistribution(values), inviteeCountDistribution=Deterministic(1) ) inviter._invite() self.assertEquals(len(client._events), 1) attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE')) expected = set(("mailto:user%02d@example.com" % (selfNumber,), "mailto:user%02d@example.com" % (otherNumber,),)) for attendee in attendees: expected.remove(attendee.value()) self.assertEqual(len(expected), 0) def test_doNotAddExistingToEvent(self): """ If the inviter randomly selects a user which is already an invitee on the event, a different user is added instead. """ calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None) selfNumber = 1 client = StubClient(selfNumber, self.mktemp()) client._calendars.update({calendar.url: calendar}) inviteeNumber = 20 anotherNumber = inviteeNumber + 5 values = [inviteeNumber - selfNumber, inviteeNumber - selfNumber, anotherNumber - selfNumber] inviter = Inviter(Clock(), self.sim, client, selfNumber) inviter.setParameters( inviteeDistribution=SequentialDistribution(values), inviteeCountDistribution=Deterministic(2) ) inviter._invite() self.assertEquals(len(client._events), 1) attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE')) expected = set(( "mailto:user%02d@example.com" % (selfNumber,), "mailto:user%02d@example.com" % (inviteeNumber,), "mailto:user%02d@example.com" % (anotherNumber,), )) for attendee in attendees: expected.remove(attendee.value()) self.assertEqual(len(expected), 0) def test_everybodyInvitedAlready(self): """ If the first so-many randomly selected users we come across are already attendees on the event, the invitation attempt is abandoned. """ calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None) userNumber = 1 client = StubClient(userNumber, self.mktemp()) client._calendars.update({calendar.url: calendar}) inviter = Inviter(Clock(), self.sim, client, userNumber) inviter.setParameters( inviteeDistribution=Deterministic(1), inviteeCountDistribution=Deterministic(2) ) inviter._invite() self.assertEquals(len(client._events), 0) test_everybodyInvitedAlready.todo = "Inviter logic has changed and it may add fewer-than-requested attendees" class AccepterTests(TestCase): """ Tests for loadtest.profiles.Accepter. """ def setUp(self): self.sim = CalendarClientSimulator( AnyUser(), Populator(None), None, None, None, None, None, None) def test_enabled(self): userNumber = 13 client = StubClient(userNumber, self.mktemp()) accepter = Accepter(None, self.sim, client, userNumber, **{"enabled": False}) self.assertEqual(accepter.enabled, False) accepter = Accepter(None, self.sim, client, userNumber, **{"enabled": True}) self.assertEqual(accepter.enabled, True) def test_ignoreEventOnUnknownCalendar(self): """ If an event on an unknown calendar changes, it is ignored. """ userNumber = 13 client = StubClient(userNumber, self.mktemp()) accepter = Accepter(None, self.sim, client, userNumber) accepter.eventChanged('/some/calendar/1234.ics') def test_ignoreNonCalendar(self): """ If an event is on a calendar which is not of type {CALDAV:}calendar, it is ignored. """ userNumber = 14 calendarURL = '/some/calendar/' calendar = Calendar( csxml.dropbox_home, set(), u'notification', calendarURL, None) client = StubClient(userNumber, self.mktemp()) client._calendars[calendarURL] = calendar accepter = Accepter(None, self.sim, client, userNumber) accepter.eventChanged(calendarURL + '1234.ics') def test_ignoreAccepted(self): """ If the client is an attendee on an event but the PARTSTAT is not NEEDS-ACTION, the event is ignored. """ vevent = Component.fromString(ACCEPTED_EVENT) attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1]) calendarURL = '/some/calendar/' calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None) client = StubClient(userNumber, self.mktemp()) client._calendars[calendarURL] = calendar event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent) client._events[event.url] = event accepter = Accepter(None, self.sim, client, userNumber) accepter.eventChanged(event.url) def test_ignoreAlreadyAccepting(self): """ If the client sees an event change a second time before responding to an invitation found on it during the first change notification, the second change notification does not generate another accept attempt. """ clock = Clock() randomDelay = 7 vevent = Component.fromString(INVITED_EVENT) attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1]) calendarURL = '/some/calendar/' calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None) client = StubClient(userNumber, self.mktemp()) client._calendars[calendarURL] = calendar event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent) client._events[event.url] = event accepter = Accepter(clock, self.sim, client, userNumber) accepter.random = Deterministic() def _gauss(mu, sigma): return randomDelay accepter.random.gauss = _gauss accepter.eventChanged(event.url) accepter.eventChanged(event.url) clock.advance(randomDelay) def test_inboxReply(self): """ When an inbox item that contains a reply is seen by the client, it deletes it immediately. """ userNumber = 1 clock = Clock() inboxURL = '/some/inbox/' vevent = Component.fromString(INBOX_REPLY) inbox = Calendar( caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None) client = StubClient(userNumber, self.mktemp()) client._calendars[inboxURL] = inbox inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent) client._setEvent(inboxEvent.url, inboxEvent) accepter = Accepter(clock, self.sim, client, userNumber) accepter.eventChanged(inboxEvent.url) clock.advance(3) self.assertNotIn(inboxEvent.url, client._events) self.assertNotIn('4321.ics', inbox.events) def test_inboxReplyFailedDelete(self): """ When an inbox item that contains a reply is seen by the client, it deletes it immediately. If the delete fails, the appropriate response code is returned. """ userNumber = 1 clock = Clock() inboxURL = '/some/inbox/' vevent = Component.fromString(INBOX_REPLY) inbox = Calendar( caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None) client = StubClient(userNumber, self.mktemp()) client._calendars[inboxURL] = inbox inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent) client._setEvent(inboxEvent.url, inboxEvent) client._failDeleteWithObject(inboxEvent.url, IncorrectResponseCode( NO_CONTENT, Response( ('HTTP', 1, 1), PRECONDITION_FAILED, 'Precondition Failed', None, None), None) ) accepter = Accepter(clock, self.sim, client, userNumber) accepter.eventChanged(inboxEvent.url) clock.advance(3) self.assertNotIn(inboxEvent.url, client._events) self.assertNotIn('4321.ics', inbox.events) def test_acceptInvitation(self): """ If the client is an attendee on an event and the PARTSTAT is NEEDS-ACTION, a response is generated which accepts the invitation and the corresponding event in the I{schedule-inbox} is deleted. """ clock = Clock() randomDelay = 7 vevent = Component.fromString(INVITED_EVENT) attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1]) client = StubClient(userNumber, self.mktemp()) calendarURL = '/some/calendar/' calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None) client._calendars[calendarURL] = calendar inboxURL = '/some/inbox/' inbox = Calendar( caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None) client._calendars[inboxURL] = inbox event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent) client._setEvent(event.url, event) inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent) client._setEvent(inboxEvent.url, inboxEvent) accepter = Accepter(clock, self.sim, client, userNumber) accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay)) accepter.eventChanged(event.url) clock.advance(randomDelay) vevent = client._events[event.url].component attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) self.assertEquals(len(attendees), 2) self.assertEquals( attendees[1].parameterValue('CN'), 'User %02d' % (userNumber,)) self.assertEquals( attendees[1].parameterValue('PARTSTAT'), 'ACCEPTED') self.assertFalse(attendees[1].hasParameter('RSVP')) self.assertNotIn(inboxEvent.url, client._events) self.assertNotIn('4321.ics', inbox.events) def test_reacceptInvitation(self): """ If a client accepts an invitation on an event and then is later re-invited to the same event, the invitation is again accepted. """ clock = Clock() randomDelay = 7 vevent = Component.fromString(INVITED_EVENT) attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1]) calendarURL = '/some/calendar/' calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None) client = StubClient(userNumber, self.mktemp()) client._calendars[calendarURL] = calendar event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent) client._events[event.url] = event accepter = Accepter(clock, self.sim, client, userNumber) accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay)) accepter.eventChanged(event.url) clock.advance(randomDelay) # Now re-set the event so it has to be accepted again event.component = Component.fromString(INVITED_EVENT) # And now re-deliver it accepter.eventChanged(event.url) clock.advance(randomDelay) # And ensure that it was accepted again vevent = client._events[event.url].component attendees = tuple(vevent.mainComponent().properties('ATTENDEE')) self.assertEquals(len(attendees), 2) self.assertEquals( attendees[1].parameterValue('CN'), 'User %02d' % (userNumber,)) self.assertEquals( attendees[1].parameterValue('PARTSTAT'), 'ACCEPTED') self.assertFalse(attendees[1].hasParameter('RSVP')) def test_changeEventAttendeePreconditionFailed(self): """ If the attempt to accept an invitation fails because of an unmet precondition (412), the event is re-retrieved and the PUT is re-issued with the new data. """ clock = Clock() userNumber = 2 client = StubClient(userNumber, self.mktemp()) randomDelay = 3 calendarURL = '/some/calendar/' calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None) client._calendars[calendarURL] = calendar vevent = Component.fromString(INVITED_EVENT) event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent) client._setEvent(event.url, event) accepter = Accepter(clock, self.sim, client, userNumber) accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay)) client.rescheduled.add(event.url) accepter.eventChanged(event.url) clock.advance(randomDelay) class EventerTests(TestCase): """ Tests for loadtest.profiles.Eventer, a profile which adds new events on calendars. """ def setUp(self): self.sim = CalendarClientSimulator( AnyUser(), Populator(None), None, None, None, None, None, None) def test_enabled(self): userNumber = 13 client = StubClient(userNumber, self.mktemp()) eventer = Eventer(None, self.sim, client, None, **{"enabled": False}) self.assertEqual(eventer.enabled, False) eventer = Eventer(None, self.sim, client, None, **{"enabled": True}) self.assertEqual(eventer.enabled, True) def test_doNotAddEventOnInbox(self): """ When the only calendar is a schedule inbox, no attempt is made to add events on it. """ calendar = Calendar( caldavxml.schedule_inbox, set(), u'inbox', u'/sched/inbox', None) client = StubClient(21, self.mktemp()) client._calendars.update({calendar.url: calendar}) eventer = Eventer(None, self.sim, client, None) eventer._addEvent() self.assertEquals(client._events, {}) def test_addEvent(self): """ When there is a normal calendar to add events to, L{Eventer._addEvent} adds an event to it. """ calendar = Calendar( caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None) client = StubClient(31, self.mktemp()) client._calendars.update({calendar.url: calendar}) eventer = Eventer(Clock(), self.sim, client, None) eventer._addEvent() self.assertEquals(len(client._events), 1) # XXX Vary the event period/interval and the uid class AttachmentDownloaderTests(TestCase): """ Tests for L{AttachmentDownloader}. """ def setUp(self): self.sim = CalendarClientSimulator( AnyUser(), Populator(None), None, None, None, None, None, None) def test_normalize(self): client = StubClient(1, self.mktemp()) client._managed_attachments_server_url = "https://thisserver:8443" downloader = AttachmentDownloader(Clock(), self.sim, client, None) self.assertEquals( downloader._normalizeHref("http://otherserver/attachment/url"), "https://thisserver:8443/attachment/url" ) class OperationLoggerTests(TestCase): """ Tests for L{OperationLogger}. """ def test_noFailures(self): """ If the median lag is below 1 second and the failure rate is below 1%, L{OperationLogger.failures} returns an empty list. """ logger = OperationLogger(outfile=StringIO()) logger.observe(dict( type='operation', phase='start', user='user01', label='testing', lag=0.5) ) logger.observe(dict( type='operation', phase='end', user='user01', duration=0.35, label='testing', success=True) ) self.assertEqual([], logger.failures()) def test_lagLimitExceeded(self): """ If the median scheduling lag for any operation in the simulation exceeds 1 second, L{OperationLogger.failures} returns a list containing a string describing that issue. """ logger = OperationLogger(outfile=StringIO()) for lag in [100.0, 1100.0, 1200.0]: logger.observe(dict( type='operation', phase='start', user='user01', label='testing', lag=lag) ) self.assertEqual( ["Median TESTING scheduling lag greater than 1000.0ms"], logger.failures()) def test_failureLimitExceeded(self): """ If the failure rate for any operation exceeds 1%, L{OperationLogger.failures} returns a list containing a string describing that issue. """ logger = OperationLogger(outfile=StringIO()) for _ignore in range(98): logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='testing', success=True) ) logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='testing', success=False) ) self.assertEqual( ["Greater than 1% TESTING failed"], logger.failures()) def test_failureNoPush(self): """ If there are no pushes, L{OperationLogger.failures} will/will not fail depending on whether the "failIfNoPush" parameter is set. """ # No pushes, no param logger = OperationLogger(outfile=StringIO()) for _ignore in range(98): logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='testing', success=True) ) self.assertEqual( [], logger.failures()) # No pushes, have param True logger = OperationLogger(outfile=StringIO(), failIfNoPush=True) for _ignore in range(98): logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='testing', success=True) ) self.assertEqual( [OperationLogger._PUSH_MISSING_REASON], logger.failures()) # Pushes, have param False logger = OperationLogger(outfile=StringIO(), failIfNoPush=False) for _ignore in range(98): logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='testing', success=True) ) logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='push', success=True) ) self.assertEqual( [], logger.failures()) # Pushes, have param True logger = OperationLogger(outfile=StringIO(), failIfNoPush=True) for _ignore in range(98): logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='testing', success=True) ) logger.observe(dict( type='operation', phase='end', user='user01', duration=0.25, label='push', success=True) ) self.assertEqual( [], logger.failures()) class AlarmAcknowledgerTests(TestCase): def test_pastTheHour(self): acknowledger = AlarmAcknowledger(None, None, None, {}) self.assertTrue(acknowledger._shouldUpdate(0)) self.assertFalse(acknowledger._shouldUpdate(0)) self.assertFalse(acknowledger._shouldUpdate(1)) self.assertFalse(acknowledger._shouldUpdate(1)) self.assertFalse(acknowledger._shouldUpdate(14)) self.assertTrue(acknowledger._shouldUpdate(15)) self.assertFalse(acknowledger._shouldUpdate(15)) self.assertFalse(acknowledger._shouldUpdate(16)) self.assertTrue(acknowledger._shouldUpdate(30)) self.assertFalse(acknowledger._shouldUpdate(30)) self.assertFalse(acknowledger._shouldUpdate(59)) self.assertTrue(acknowledger._shouldUpdate(0)) self.assertFalse(acknowledger._shouldUpdate(0))