[CalendarServer-changes] [15048] CalendarServer/branches/users/sredmond/clientsim/contrib/ performance/loadtest
source_changes at macosforge.org
source_changes at macosforge.org
Mon Aug 17 14:14:42 PDT 2015
Revision: 15048
http://trac.calendarserver.org//changeset/15048
Author: sredmond at apple.com
Date: 2015-08-17 14:14:42 -0700 (Mon, 17 Aug 2015)
Log Message:
-----------
Cleans up old files from trunk
Modified Paths:
--------------
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request
Added Paths:
-----------
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/calendars-only.plist
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/clients-old.plist
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py
Removed Paths:
-------------
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/clients.plist
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.dist.plist
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.plist
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_ical.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_population.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_profiles.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_sim.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_trafficlogger.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_webadmin.py
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/clients.plist
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/clients.plist 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/clients.plist 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,548 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
- Copyright (c) 2011-2015 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.
- -->
-
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
- <dict>
- <!-- Define the kinds of software and user behavior the load simulation
- will simulate. -->
- <key>clients</key>
-
- <!-- Have as many different kinds of software and user behavior configurations
- as you want. Each is a dict -->
- <array>
-
- <dict>
-
- <!-- Here is a OS X client simulator. -->
- <key>software</key>
- <string>contrib.performance.loadtest.ical.OS_X_10_7</string>
-
- <!-- Arguments to use to initialize the OS_X_10_7 instance. -->
- <key>params</key>
- <dict>
- <!-- Name that appears in logs. -->
- <key>title</key>
- <string>10.7</string>
-
- <!-- OS_X_10_7 can poll the calendar home at some interval. This is
- in seconds. -->
- <key>calendarHomePollInterval</key>
- <integer>30</integer>
-
- <!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications
- about calendar home changes instead of polling for them periodically. If
- this option is true, then look for the server advertisement for xmpp push
- and use it if possible. Still fall back to polling if there is no xmpp push
- advertised. -->
- <key>supportPush</key>
- <false />
-
- <key>supportAmpPush</key>
- <true/>
- <key>ampPushHost</key>
- <string>localhost</string>
- <key>ampPushPort</key>
- <integer>62311</integer>
- </dict>
-
- <!-- The profiles define certain types of user behavior on top of the
- client software being simulated. -->
- <key>profiles</key>
- <array>
-
- <!-- First an event-creating profile, which will periodically create
- new events at a random time on a random calendar. -->
- <dict>
- <key>class</key>
- <string>contrib.performance.loadtest.profiles.Eventer</string>
-
- <key>params</key>
- <dict>
- <key>enabled</key>
- <true/>
-
- <!-- Define the interval (in seconds) at which this profile will use
- its client to create a new event. -->
- <key>interval</key>
- <integer>60</integer>
-
- <!-- Define how start times (DTSTART) for the randomly generated events
- will be selected. This is an example of a "Distribution" parameter. The value
- for most "Distribution" parameters are interchangeable and extensible. -->
- <key>eventStartDistribution</key>
- <dict>
-
- <!-- This distribution is pretty specialized. It produces timestamps
- in the near future, limited to certain days of the week and certain hours
- of the day. -->
- <key>type</key>
- <string>contrib.performance.stats.WorkDistribution</string>
-
- <key>params</key>
- <dict>
- <!-- These are the days of the week the distribution will use. -->
- <key>daysOfWeek</key>
- <array>
- <string>mon</string>
- <string>tue</string>
- <string>wed</string>
- <string>thu</string>
- <string>fri</string>
- </array>
-
- <!-- The earliest hour of a day at which an event might be scheduled. -->
- <key>beginHour</key>
- <integer>8</integer>
-
- <!-- And the latest hour of a day (at which an event will be scheduled
- to begin!). -->
- <key>endHour</key>
- <integer>16</integer>
-
- <!-- The timezone in which the event is scheduled. (XXX Does this
- really work right?) -->
- <key>tzname</key>
- <string>America/Los_Angeles</string>
- </dict>
- </dict>
-
- <!-- Define how recurrences are created. -->
- <key>recurrenceDistribution</key>
- <dict>
-
- <!-- This distribution is pretty specialized. We have a fixed set of
- RRULEs defined for this distribution and pick each based on a
- weight. -->
- <key>type</key>
- <string>contrib.performance.stats.RecurrenceDistribution</string>
-
- <key>params</key>
- <dict>
- <!-- False to disable RRULEs -->
- <key>allowRecurrence</key>
- <true/>
-
- <!-- These are the weights for the specific set of RRULEs. -->
- <key>weights</key>
- <dict>
- <!-- Half of all events will be non-recurring -->
- <key>none</key>
- <integer>50</integer>
-
- <!-- Daily and weekly are pretty common -->
- <key>daily</key>
- <integer>10</integer>
- <key>weekly</key>
- <integer>20</integer>
-
- <!-- Monthly, yearly, daily & weekly limit not so common -->
- <key>monthly</key>
- <integer>2</integer>
- <key>yearly</key>
- <integer>1</integer>
- <key>dailylimit</key>
- <integer>2</integer>
- <key>weeklylimit</key>
- <integer>5</integer>
-
- <!-- Work days pretty common -->
- <key>workdays</key>
- <integer>10</integer>
- </dict>
- </dict>
- </dict>
- </dict>
- </dict>
-
- <!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. -->
- <dict>
- <key>class</key>
- <string>contrib.performance.loadtest.profiles.EventUpdater</string>
-
- <key>params</key>
- <dict>
- <key>enabled</key>
- <false/>
-
- <!-- Define the interval (in seconds) at which this profile will use
- its client to create a new event. -->
- <key>interval</key>
- <integer>5</integer>
-
- <!-- Define how start times (DTSTART) for the randomly generated events
- will be selected. This is an example of a "Distribution" parameter. The value
- for most "Distribution" parameters are interchangeable and extensible. -->
- <key>eventStartDistribution</key>
- <dict>
-
- <!-- This distribution is pretty specialized. It produces timestamps
- in the near future, limited to certain days of the week and certain hours
- of the day. -->
- <key>type</key>
- <string>contrib.performance.stats.WorkDistribution</string>
-
- <key>params</key>
- <dict>
- <!-- These are the days of the week the distribution will use. -->
- <key>daysOfWeek</key>
- <array>
- <string>mon</string>
- <string>tue</string>
- <string>wed</string>
- <string>thu</string>
- <string>fri</string>
- </array>
-
- <!-- The earliest hour of a day at which an event might be scheduled. -->
- <key>beginHour</key>
- <integer>8</integer>
-
- <!-- And the latest hour of a day (at which an event will be scheduled
- to begin!). -->
- <key>endHour</key>
- <integer>16</integer>
-
- <!-- The timezone in which the event is scheduled. (XXX Does this
- really work right?) -->
- <key>tzname</key>
- <string>America/Los_Angeles</string>
- </dict>
- </dict>
-
- <!-- Define how recurrences are created. -->
- <key>recurrenceDistribution</key>
- <dict>
-
- <!-- This distribution is pretty specialized. We have a fixed set of
- RRULEs defined for this distribution and pick each based on a
- weight. -->
- <key>type</key>
- <string>contrib.performance.stats.RecurrenceDistribution</string>
-
- <key>params</key>
- <dict>
- <!-- False to disable RRULEs -->
- <key>allowRecurrence</key>
- <true/>
-
- <!-- These are the weights for the specific set of RRULEs. -->
- <key>weights</key>
- <dict>
- <!-- Half of all events will be non-recurring -->
- <key>none</key>
- <integer>50</integer>
-
- <!-- Daily and weekly are pretty common -->
- <key>daily</key>
- <integer>25</integer>
- <key>weekly</key>
- <integer>25</integer>
-
- <!-- Monthly, yearly, daily & weekly limit not so common -->
- <key>monthly</key>
- <integer>0</integer>
- <key>yearly</key>
- <integer>0</integer>
- <key>dailylimit</key>
- <integer>0</integer>
- <key>weeklylimit</key>
- <integer>0</integer>
-
- <!-- Work days pretty common -->
- <key>workdays</key>
- <integer>0</integer>
- </dict>
- </dict>
- </dict>
- </dict>
- </dict>
-
- <!-- This profile invites some number of new attendees to new events. -->
- <dict>
- <key>class</key>
- <string>contrib.performance.loadtest.profiles.RealisticInviter</string>
-
- <key>params</key>
- <dict>
- <key>enabled</key>
- <true/>
-
- <!-- Define the frequency at which new invitations will be sent out. -->
- <key>sendInvitationDistribution</key>
- <dict>
- <key>type</key>
- <string>contrib.performance.stats.NormalDistribution</string>
- <key>params</key>
- <dict>
- <!-- mu gives the mean of the normal distribution (in seconds). -->
- <key>mu</key>
- <integer>60</integer>
-
- <!-- and sigma gives its standard deviation. -->
- <key>sigma</key>
- <integer>5</integer>
- </dict>
- </dict>
-
- <!-- Define the distribution of who will be invited to an event.
-
- When inviteeClumping is turned on each invitee is based on a sample of
- users "close to" the organizer based on account index. If the clumping
- is too "tight" for the requested number of attendees, then invites for
- those larger numbers will simply fail (the sim will report that situation).
-
- When inviteeClumping is off invitees will be sampled across an entire
- range of account indexes. In this case the distribution ought to be a
- UniformIntegerDistribution with min=0 and max set to the number of accounts.
- -->
- <key>inviteeDistribution</key>
- <dict>
- <key>type</key>
- <string>contrib.performance.stats.UniformIntegerDistribution</string>
- <key>params</key>
- <dict>
- <!-- The minimum value (inclusive) of the uniform distribution. -->
- <key>min</key>
- <integer>0</integer>
- <!-- The maximum value (exclusive) of the uniform distribution. -->
- <key>max</key>
- <integer>99</integer>
- </dict>
- </dict>
-
- <key>inviteeClumping</key>
- <true/>
-
- <!-- Define the distribution of how many attendees will be invited to an event.
-
- LogNormal is the best fit to observed data.
-
-
- For LogNormal "mode" is the peak, "mean" is the mean value. For invites,
- mode should typically be 1, and mean whatever matches the user behavior.
- Our typical mean is 6.
- -->
- <key>inviteeCountDistribution</key>
- <dict>
- <key>type</key>
- <string>contrib.performance.stats.LogNormalDistribution</string>
- <key>params</key>
- <dict>
- <!-- mode - peak-->
- <key>mode</key>
- <integer>1</integer>
- <!-- mean - average-->
- <key>median</key>
- <integer>6</integer>
- <!-- maximum -->
- <key>maximum</key>
- <real>60</real>
- </dict>
- </dict>
-
- <!-- Define how start times (DTSTART) for the randomly generated events
- will be selected. This is an example of a "Distribution" parameter. The value
- for most "Distribution" parameters are interchangeable and extensible. -->
- <key>eventStartDistribution</key>
- <dict>
-
- <!-- This distribution is pretty specialized. It produces timestamps
- in the near future, limited to certain days of the week and certain hours
- of the day. -->
- <key>type</key>
- <string>contrib.performance.stats.WorkDistribution</string>
-
- <key>params</key>
- <dict>
- <!-- These are the days of the week the distribution will use. -->
- <key>daysOfWeek</key>
- <array>
- <string>mon</string>
- <string>tue</string>
- <string>wed</string>
- <string>thu</string>
- <string>fri</string>
- </array>
-
- <!-- The earliest hour of a day at which an event might be scheduled. -->
- <key>beginHour</key>
- <integer>8</integer>
-
- <!-- And the latest hour of a day (at which an event will be scheduled
- to begin!). -->
- <key>endHour</key>
- <integer>16</integer>
-
- <!-- The timezone in which the event is scheduled. (XXX Does this
- really work right?) -->
- <key>tzname</key>
- <string>America/Los_Angeles</string>
- </dict>
- </dict>
-
- <!-- Define how recurrences are created. -->
- <key>recurrenceDistribution</key>
- <dict>
-
- <!-- This distribution is pretty specialized. We have a fixed set of
- RRULEs defined for this distribution and pick each based on a
- weight. -->
- <key>type</key>
- <string>contrib.performance.stats.RecurrenceDistribution</string>
-
- <key>params</key>
- <dict>
- <!-- False to disable RRULEs -->
- <key>allowRecurrence</key>
- <true/>
-
- <!-- These are the weights for the specific set of RRULEs. -->
- <key>weights</key>
- <dict>
- <!-- Half of all events will be non-recurring -->
- <key>none</key>
- <integer>50</integer>
-
- <!-- Daily and weekly are pretty common -->
- <key>daily</key>
- <integer>10</integer>
- <key>weekly</key>
- <integer>20</integer>
-
- <!-- Monthly, yearly, daily & weekly limit not so common -->
- <key>monthly</key>
- <integer>2</integer>
- <key>yearly</key>
- <integer>1</integer>
- <key>dailylimit</key>
- <integer>2</integer>
- <key>weeklylimit</key>
- <integer>5</integer>
-
- <!-- Work days pretty common -->
- <key>workdays</key>
- <integer>10</integer>
- </dict>
- </dict>
- </dict>
- </dict>
- </dict>
-
- <!-- This profile accepts invitations to events, handles cancels, and
- handles replies received. -->
- <dict>
- <key>class</key>
- <string>contrib.performance.loadtest.profiles.Accepter</string>
-
- <key>params</key>
- <dict>
- <key>enabled</key>
- <true/>
-
- <!-- Define how long to wait after seeing a new invitation before
- accepting it.
-
- For LogNormal "mode" is the peak, "median" is the 50% cummulative value
- (i.e., half of the user have accepted by that time).
- -->
- <key>acceptDelayDistribution</key>
- <dict>
- <key>type</key>
- <string>contrib.performance.stats.LogNormalDistribution</string>
- <key>params</key>
- <dict>
- <!-- mode - peak-->
- <key>mode</key>
- <integer>300</integer>
- <!-- median - 50% done-->
- <key>median</key>
- <integer>1800</integer>
- </dict>
- </dict>
- </dict>
- </dict>
-
- <!-- A task-creating profile, which will periodically create
- new tasks at a random time on a random calendar. -->
- <dict>
- <key>class</key>
- <string>contrib.performance.loadtest.profiles.Tasker</string>
-
- <key>params</key>
- <dict>
- <key>enabled</key>
- <true/>
-
- <!-- Define the interval (in seconds) at which this profile will use
- its client to create a new task. -->
- <key>interval</key>
- <integer>300</integer>
-
- <!-- Define how due times (DUE) for the randomly generated tasks
- will be selected. This is an example of a "Distribution" parameter. The value
- for most "Distribution" parameters are interchangeable and extensible. -->
- <key>taskDueDistribution</key>
- <dict>
-
- <!-- This distribution is pretty specialized. It produces timestamps
- in the near future, limited to certain days of the week and certain hours
- of the day. -->
- <key>type</key>
- <string>contrib.performance.stats.WorkDistribution</string>
-
- <key>params</key>
- <dict>
- <!-- These are the days of the week the distribution will use. -->
- <key>daysOfWeek</key>
- <array>
- <string>mon</string>
- <string>tue</string>
- <string>wed</string>
- <string>thu</string>
- <string>fri</string>
- </array>
-
- <!-- The earliest hour of a day at which an event might be scheduled. -->
- <key>beginHour</key>
- <integer>8</integer>
-
- <!-- And the latest hour of a day (at which an event will be scheduled
- to begin!). -->
- <key>endHour</key>
- <integer>16</integer>
-
- <!-- The timezone in which the event is scheduled. (XXX Does this
- really work right?) -->
- <key>tzname</key>
- <string>America/Los_Angeles</string>
- </dict>
- </dict>
- </dict>
- </dict>
-
- </array>
-
- <!-- Determine the frequency at which this client configuration will
- appear in the clients which are created by the load tester. -->
- <key>weight</key>
- <integer>1</integer>
- </dict>
- </array>
- </dict>
-</plist>
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.dist.plist
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.dist.plist 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.dist.plist 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,188 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
- Copyright (c) 2011-2015 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.
- -->
-
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
- <dict>
- <!-- This is a distributed orchestrator configuration; 'workers' is a list of
- shell commands to run sub-processes.
- -->
- <key>workers</key>
- <array>
- <string>./bin/python contrib/performance/loadtest/ampsim.py</string>
- <string>./bin/python contrib/performance/loadtest/ampsim.py</string>
- <string>./bin/python contrib/performance/loadtest/ampsim.py</string>
- <string>./bin/python contrib/performance/loadtest/ampsim.py</string>
- <string>./bin/python contrib/performance/loadtest/ampsim.py</string>
- <string>./bin/python contrib/performance/loadtest/ampsim.py</string>
- </array>
-
- <!-- Identify the server to be load tested. -->
- <key>server</key>
- <string>https://127.0.0.1:8443</string>
-
- <!-- The template URI for doing initial principal lookup on. -->
- <key>principalPathTemplate</key>
- <string>/principals/users/%s/</string>
-
- <!-- Configure Admin Web UI. -->
- <key>webadmin</key>
- <dict>
- <key>enabled</key>
- <true/>
-
- <key>HTTPPort</key>
- <integer>8080</integer>
- </dict>
-
- <!-- Define whether server supports stats socket. -->
- <key>serverStats</key>
- <dict>
- <key>enabled</key>
- <true/>
- <key>Port</key>
- <integer>8100</integer>
- </dict>
-
- <!-- Define whether client data should be re-used. It will always be saved to the specified path.-->
- <key>clientDataSerialization</key>
- <dict>
- <key>UseOldData</key>
- <true/>
- <key>Path</key>
- <string>/tmp/sim</string>
- </dict>
-
- <!-- Define the credentials of the clients which will be used to load test
- the server. These credentials must already be valid on the server. -->
- <key>accounts</key>
- <dict>
- <!-- The loader is the fully-qualified Python name of a callable which
- returns a list of directory service records defining all of the client accounts
- to use. contrib.performance.loadtest.sim.recordsFromCSVFile reads username,
- password, mailto triples from a CSV file and returns them as a list of faked
- directory service records. -->
- <key>loader</key>
- <string>contrib.performance.loadtest.sim.recordsFromCSVFile</string>
-
- <!-- Keyword arguments may be passed to the loader. -->
- <key>params</key>
- <dict>
- <!-- recordsFromCSVFile interprets the path relative to the config.plist,
- to make it independent of the script's working directory while still allowing
- a relative path. This isn't a great solution. -->
- <key>path</key>
- <string>contrib/performance/loadtest/accounts.csv</string>
- </dict>
- </dict>
-
- <!-- Define how many clients will participate in the load test and how
- they will show up. -->
- <key>arrival</key>
- <dict>
-
- <!-- Specify a class which creates new clients and introduces them into
- the test. contrib.performance.loadtest.population.SmoothRampUp introduces
- groups of new clients at fixed intervals up to a maximum. The size of the
- group, interval, and maximum are configured by the parameters below. The
- total number of clients is groups * groupSize, which needs to be no larger
- than the number of credentials created in the accounts section. -->
- <key>factory</key>
- <string>contrib.performance.loadtest.population.SmoothRampUp</string>
-
- <key>params</key>
- <dict>
- <!-- groups gives the total number of groups of clients to introduce. -->
- <key>groups</key>
- <integer>99</integer>
-
- <!-- groupSize is the number of clients in each group of clients. It's
- really only a "smooth" ramp up if this is pretty small. -->
- <key>groupSize</key>
- <integer>1</integer>
-
- <!-- Number of seconds between the introduction of each group. -->
- <key>interval</key>
- <integer>3</integer>
-
- <!-- Number of clients each user is assigned to. -->
- <!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
- <key>clientsPerUser</key>
- <integer>1</integer>
- </dict>
-
- </dict>
-
- <!-- Define some log observers to report on the load test. -->
- <key>observers</key>
- <array>
- <!-- ReportStatistics generates an end-of-run summary of the HTTP requests
- made, their timings, and their results. -->
- <dict>
- <key>type</key>
- <string>contrib.performance.loadtest.population.ReportStatistics</string>
- <key>params</key>
- <dict>
- <!-- The thresholds for each request type -->
- <key>thresholdsPath</key>
- <string>contrib/performance/loadtest/thresholds.json</string>
-
- <!-- The benchmarks for overall QoS -->
- <key>benchmarksPath</key>
- <string>contrib/performance/loadtest/benchmarks.json</string>
-
- <!-- The % of failures that constitute a failed test -->
- <key>failCutoff</key>
- <real>1.0</real>
- </dict>
- </dict>
-
- <!-- RequestLogger generates a realtime log of all HTTP requests made
- during the load test. -->
- <dict>
- <key>type</key>
- <string>contrib.performance.loadtest.ical.RequestLogger</string>
- <key>params</key>
- <dict>
- </dict>
- </dict>
-
- <!-- OperationLogger generates an end-of-run summary of the gross operations
- performed (logical operations which may span more than one HTTP request,
- such as inviting an attendee to an event). -->
- <dict>
- <key>type</key>
- <string>contrib.performance.loadtest.profiles.OperationLogger</string>
- <key>params</key>
- <dict>
- <!-- The thresholds for each operation type -->
- <key>thresholdsPath</key>
- <string>contrib/performance/loadtest/thresholds.json</string>
-
- <!-- The % of operations beyond the lag cut-off that constitute a failed test -->
- <key>lagCutoff</key>
- <real>1.0</real>
-
- <!-- The % of failures that constitute a failed test -->
- <key>failCutoff</key>
- <real>1.0</real>
- </dict>
- </dict>
- </array>
- </dict>
-</plist>
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.plist 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.plist 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,175 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
- Copyright (c) 2011-2015 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.
- -->
-
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
- <dict>
- <!-- Identify the server to be load tested. -->
- <key>server</key>
- <string>https://127.0.0.1:8443</string>
-
- <!-- The template URI for doing initial principal lookup on. -->
- <key>principalPathTemplate</key>
- <string>/principals/users/%s/</string>
-
- <!-- Configure Admin Web UI. -->
- <key>webadmin</key>
- <dict>
- <key>enabled</key>
- <true/>
-
- <key>HTTPPort</key>
- <integer>8080</integer>
- </dict>
-
- <!-- Define whether server supports stats socket. -->
- <key>serverStats</key>
- <dict>
- <key>enabled</key>
- <true/>
- <key>Port</key>
- <integer>8100</integer>
- </dict>
-
- <!-- Define whether client data should be re-used. It will always be saved to the specified path.-->
- <key>clientDataSerialization</key>
- <dict>
- <key>UseOldData</key>
- <true/>
- <key>Path</key>
- <string>/tmp/sim</string>
- </dict>
-
- <!-- Define the credentials of the clients which will be used to load test
- the server. These credentials must already be valid on the server. -->
- <key>accounts</key>
- <dict>
- <!-- The loader is the fully-qualified Python name of a callable which
- returns a list of directory service records defining all of the client accounts
- to use. contrib.performance.loadtest.sim.recordsFromCSVFile reads username,
- password, mailto triples from a CSV file and returns them as a list of faked
- directory service records. -->
- <key>loader</key>
- <string>contrib.performance.loadtest.sim.recordsFromCSVFile</string>
-
- <!-- Keyword arguments may be passed to the loader. -->
- <key>params</key>
- <dict>
- <!-- recordsFromCSVFile interprets the path relative to the config.plist,
- to make it independent of the script's working directory while still allowing
- a relative path. This isn't a great solution. -->
- <key>path</key>
- <string>contrib/performance/loadtest/accounts.csv</string>
- </dict>
- </dict>
-
- <!-- Define how many clients will participate in the load test and how
- they will show up. -->
- <key>arrival</key>
- <dict>
-
- <!-- Specify a class which creates new clients and introduces them into
- the test. contrib.performance.loadtest.population.SmoothRampUp introduces
- groups of new clients at fixed intervals up to a maximum. The size of the
- group, interval, and maximum are configured by the parameters below. The
- total number of clients is groups * groupSize, which needs to be no larger
- than the number of credentials created in the accounts section. -->
- <key>factory</key>
- <string>contrib.performance.loadtest.population.SmoothRampUp</string>
-
- <key>params</key>
- <dict>
- <!-- groups gives the total number of groups of clients to introduce. -->
- <key>groups</key>
- <integer>20</integer>
-
- <!-- groupSize is the number of clients in each group of clients. It's
- really only a "smooth" ramp up if this is pretty small. -->
- <key>groupSize</key>
- <integer>1</integer>
-
- <!-- Number of seconds between the introduction of each group. -->
- <key>interval</key>
- <integer>3</integer>
-
- <!-- Number of clients each user is assigned to. -->
- <!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
- <key>clientsPerUser</key>
- <integer>1</integer>
- </dict>
-
- </dict>
-
- <!-- Define some log observers to report on the load test. -->
- <key>observers</key>
- <array>
- <!-- ReportStatistics generates an end-of-run summary of the HTTP requests
- made, their timings, and their results. -->
- <dict>
- <key>type</key>
- <string>contrib.performance.loadtest.population.ReportStatistics</string>
- <key>params</key>
- <dict>
- <!-- The thresholds for each request type -->
- <key>thresholdsPath</key>
- <string>contrib/performance/loadtest/thresholds.json</string>
-
- <!-- The benchmarks for overall QoS -->
- <key>benchmarksPath</key>
- <string>contrib/performance/loadtest/benchmarks.json</string>
-
- <!-- The % of failures that constitute a failed test -->
- <key>failCutoff</key>
- <real>1.0</real>
- </dict>
- </dict>
-
- <!-- RequestLogger generates a realtime log of all HTTP requests made
- during the load test. -->
- <dict>
- <key>type</key>
- <string>contrib.performance.loadtest.ical.RequestLogger</string>
- <key>params</key>
- <dict>
- </dict>
- </dict>
-
- <!-- OperationLogger generates an end-of-run summary of the gross operations
- performed (logical operations which may span more than one HTTP request,
- such as inviting an attendee to an event). -->
- <dict>
- <key>type</key>
- <string>contrib.performance.loadtest.profiles.OperationLogger</string>
- <key>params</key>
- <dict>
- <!-- The thresholds for each operation type -->
- <key>thresholdsPath</key>
- <string>contrib/performance/loadtest/thresholds.json</string>
-
- <!-- The % of operations beyond the lag cut-off that constitute a failed test -->
- <key>lagCutoff</key>
- <real>1.0</real>
-
- <!-- The % of failures that constitute a failed test -->
- <key>failCutoff</key>
- <real>1.0</real>
- </dict>
- </dict>
- </array>
- </dict>
-</plist>
Added: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py (rev 0)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -0,0 +1,432 @@
+##
+# Copyright (c) 2010-2015 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.
+##
+
+"""
+Implementation of a statistics library for Calendar performance analysis.
+Exports:
+Base statistical functions
+ mean / median / stddev / mad / residuals
+
+IPopulation interface exposes:
+ sample()
+
+Sampling from this distribution must *not* change the underlying behavior of a distribution
+
+Distributions (all of which implement IPopulation):
+ UniformDiscreteDistribution
+ LogNormalDistribution
+ FixedDistribution
+ NearFutureDistribution
+ NormalDistribution
+ UniformIntegerDistribution
+ WorkDistribution
+ RecurrenceDistribution
+"""
+from math import log, sqrt
+from time import mktime
+import random
+import numpy.random as nprandom
+
+from pycalendar.datetime import DateTime
+from pycalendar.duration import Duration as PyDuration
+from pycalendar.icalendar.property import Property
+from pycalendar.timezone import Timezone
+
+from zope.interface import Interface, implements
+from twisted.python.util import FancyEqMixin
+
+
+class IPopulation(Interface):
+ """Interface for a class that provides a single function, `sample`, which returns a float"""
+ def sample(): #@NoSelf
+ pass
+
+
+class UniformDiscreteDistribution(object, FancyEqMixin):
+ """
+
+ """
+ implements(IPopulation)
+
+ compareAttributes = ['_values']
+
+ def __init__(self, values, randomize=True):
+ self._values = values
+ self._randomize = randomize
+ self._refill()
+
+
+ def _refill(self):
+ self._remaining = self._values[:]
+ if self._randomize:
+ random.shuffle(self._remaining)
+
+
+ def sample(self):
+ if not self._remaining:
+ self._refill()
+ return self._remaining.pop()
+
+
+
+class LogNormalDistribution(object, FancyEqMixin):
+ """
+ """
+ implements(IPopulation)
+
+ compareAttributes = ['_mu', '_sigma', '_maximum']
+
+ def __init__(self, mu=None, sigma=None, mean=None, mode=None, median=None, maximum=None):
+
+ if mu is not None and sigma is not None:
+ scale = 1.0
+ elif not (mu is None and sigma is None):
+ raise ValueError("mu and sigma must both be defined or both not defined")
+ elif mode is None:
+ raise ValueError("When mu and sigma are not defined, mode must be defined")
+ elif median is not None:
+ scale = mode
+ median /= mode
+ mode = 1.0
+ mu = log(median)
+ sigma = sqrt(log(median) - log(mode))
+ elif mean is not None:
+ scale = mode
+ mean /= mode
+ mode = 1.0
+ mu = log(mean) + log(mode) / 2.0
+ sigma = sqrt(log(mean) - log(mode) / 2.0)
+ else:
+ raise ValueError("When using mode one of median or mean must be defined")
+
+ self._mu = mu
+ self._sigma = sigma
+ self._scale = scale
+ self._maximum = maximum
+
+
+ def sample(self):
+ result = self._scale * random.lognormvariate(self._mu, self._sigma)
+ if self._maximum is not None and result > self._maximum:
+ for _ignore_i in range(10):
+ result = self._scale * random.lognormvariate(self._mu, self._sigma)
+ if result <= self._maximum:
+ break
+ else:
+ raise ValueError("Unable to generate LogNormalDistribution sample within required range")
+ return result
+
+
+
+class FixedDistribution(object, FancyEqMixin):
+ """
+ """
+ implements(IPopulation)
+
+ compareAttributes = ['_value']
+
+ def __init__(self, value):
+ self._value = value
+
+
+ def sample(self):
+ return self._value
+
+
+
+class NearFutureDistribution(object, FancyEqMixin):
+ compareAttributes = ['_offset']
+
+ def __init__(self):
+ self._offset = LogNormalDistribution(7, 0.8)
+
+
+ def sample(self):
+ now = DateTime.getNowUTC()
+ now.offsetSeconds(int(self._offset.sample()))
+ return now
+
+
+
+class NormalDistribution(object, FancyEqMixin):
+ compareAttributes = ['_mu', '_sigma']
+
+ def __init__(self, mu, sigma):
+ self._mu = mu
+ self._sigma = sigma
+
+
+ def sample(self):
+ # Only return positive values or zero
+ v = random.normalvariate(self._mu, self._sigma)
+ while v < 0:
+ v = random.normalvariate(self._mu, self._sigma)
+ return v
+
+
+
+class UniformIntegerDistribution(object, FancyEqMixin):
+ compareAttributes = ['_min', '_max']
+
+ def __init__(self, min, max):
+ self._min = min
+ self._max = max
+
+
+ def sample(self):
+ return int(random.uniform(self._min, self._max))
+
+
+class UniformRealDistribution(object, FancyEqMixin):
+ compareAttributes = ['_min', '_max']
+
+ def __init__(self, min, max):
+ self._min = min
+ self._max = max
+
+
+ def sample(self):
+ return random.uniform(self._min, self._max)
+
+
+class BernoulliDistribution(object, FancyEqMixin):
+ compareAttributes = ["_p"]
+
+ def __init__(self, proportion=0.5):
+ """Initializes a bernoulli distribution with success probability given by p
+ Prereq: 0 <= p <= 1
+ Returns 1 with probability p, 0 with probability q = 1-p
+ """
+ self._p = proportion
+
+ def sample(self):
+ return 1 if random.random() <= self._p else 0
+
+
+
+class BinomialDistribution(object, FancyEqMixin):
+ compareAttributes = ["_successProbability", "_numTrials"]
+
+ def __init__(self, p=0.5, n=10):
+ self._successProbability = p
+ self._numTrials = n
+
+ def sample(self):
+ return nprandom.binomial(self._numTrials, self._successProbability)
+
+
+
+class TriangularDistribution(object, FancyEqMixin):
+ compareAttributes = ["_left", "_mode", "_right"]
+
+ def __init__(self, left, mode, right):
+ self._left = left
+ self._mode = mode
+ self._right = right
+
+ def sample(self):
+ return nprandom.triangular(self._left, self._mode, self._right)
+
+
+class GeometricDistribution(object, FancyEqMixin):
+ """
+ Expected number of Bernoulli trials before the first success
+ """
+ compareAttributes = ["_p"]
+ def __init__(self, proportion=0.5):
+ self._p = proportion
+
+ def sample(self):
+ return nprandom.geometric(self._p)
+
+
+class PoissonDistribution(object, FancyEqMixin):
+ compareAttributes = ["_lambda"]
+ def __init__(self, lam):
+ self._lambda = lam
+
+ def sample(self):
+ return nprandom.possion(self._lambda)
+
+
+class BetaDistribution(object, FancyEqMixin):
+ compareAttributes = ["_alpha", "_beta"]
+ def __init__(self, alpha, beta):
+ self._alpha = alpha
+ self._beta = beta
+
+ def sample(self):
+ return nprandom.beta(self._alpha, self._beta)
+
+
+class ChiSquaredDistribution(object, FancyEqMixin):
+ compareAttributes = ["_df"]
+ def __init__(self, degreesOfFreedom):
+ self._df = degreesOfFreedom
+
+ def sample(self):
+ return nprandom.chisquare(self._df)
+
+
+class ExponentialDistribution(object, FancyEqMixin):
+ compareAttributes = ["_scale"]
+ def __init__(self, scale):
+ self._scale = scale
+
+ def sample(self):
+ return nprandom.exponential(self._scale)
+
+
+
+class GammaDistribution(object, FancyEqMixin):
+ compareAttributes = ["_shape", "_scale"]
+ def __init__(self, shape, scale=1.0):
+ self._shape = shape
+ self._scale = scale
+
+ def sample(self):
+ return nprandom.gamma(self._shape, self._scale)
+
+NUM_WEEKDAYS = 7
+
+class WorkDistribution(object, FancyEqMixin):
+ compareAttributes = ["_daysOfWeek", "_beginHour", "_endHour"]
+
+ _weekdayNames = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
+
+ def __init__(self, daysOfWeek=["mon", "tue", "wed", "thu", "fri"], beginHour=8, endHour=17, tzname="UTC"):
+ self._daysOfWeek = [self._weekdayNames.index(day) for day in daysOfWeek]
+ self._beginHour = beginHour
+ self._endHour = endHour
+ self._tzname = tzname
+ self._helperDistribution = NormalDistribution(
+ # Mean 6 workdays in the future
+ 60 * 60 * 8 * 6,
+ # Standard deviation of 4 workdays
+ 60 * 60 * 8 * 4)
+ self.now = DateTime.getNow
+
+
+ def astimestamp(self, dt):
+ return mktime(dt.timetuple())
+
+
+ def _findWorkAfter(self, when):
+ """
+ Return a two-tuple of the start and end of work hours following
+ C{when}. If C{when} falls within work hours, then the start time will
+ be equal to when.
+ """
+ # Find a workday that follows the timestamp
+ weekday = when.getDayOfWeek()
+ for i in range(NUM_WEEKDAYS):
+ day = when + PyDuration(days=i)
+ if (weekday + i) % NUM_WEEKDAYS in self._daysOfWeek:
+ # Joy, a day on which work might occur. Find the first hour on
+ # this day when work may start.
+ day.setHHMMSS(self._beginHour, 0, 0)
+ begin = day
+ end = begin.duplicate()
+ end.setHHMMSS(self._endHour, 0, 0)
+ if end > when:
+ return begin, end
+
+
+ def sample(self):
+ offset = PyDuration(seconds=int(self._helperDistribution.sample()))
+ beginning = self.now(Timezone(tzid=self._tzname))
+ while offset:
+ start, end = self._findWorkAfter(beginning)
+ if end - start > offset:
+ result = start + offset
+ result.setMinutes(result.getMinutes() // 15 * 15)
+ result.setSeconds(0)
+ return result
+ offset.setDuration(offset.getTotalSeconds() - (end - start).getTotalSeconds())
+ beginning = end
+
+
+
+class RecurrenceDistribution(object, FancyEqMixin):
+ compareAttributes = ["_allowRecurrence", "_weights"]
+
+ _model_rrules = {
+ "none": None,
+ "daily": "RRULE:FREQ=DAILY",
+ "weekly": "RRULE:FREQ=WEEKLY",
+ "monthly": "RRULE:FREQ=MONTHLY",
+ "yearly": "RRULE:FREQ=YEARLY",
+ "dailylimit": "RRULE:FREQ=DAILY;COUNT=14",
+ "weeklylimit": "RRULE:FREQ=WEEKLY;COUNT=4",
+ "workdays": "RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR"
+ }
+
+ def __init__(self, allowRecurrence, weights={}):
+ self._allowRecurrence = allowRecurrence
+ self._rrules = []
+ if self._allowRecurrence:
+ for rrule, count in sorted(weights.items(), key=lambda x: x[0]):
+ for _ignore in range(count):
+ self._rrules.append(self._model_rrules[rrule])
+ self._helperDistribution = UniformIntegerDistribution(0, len(self._rrules) - 1)
+
+
+ def sample(self):
+
+ if self._allowRecurrence:
+ index = self._helperDistribution.sample()
+ rrule = self._rrules[index]
+ if rrule:
+ prop = Property.parseText(rrule)
+ return prop
+
+ return None
+
+class HappyFaceStartDistribution(object, FancyEqMixin):
+ compareAttributes = []
+ def __init__(self, scale):
+ self._scale = scale
+
+ def sample(self):
+ return nprandom.exponential(self._scale)
+
+class HappyFaceDurationDistribution(object, FancyEqMixin):
+ compareAttributes = []
+ def __init__(self):
+ self._distribution = UniformDiscreteDistribution(60 * 15, 60 * 30)
+
+ def sample(self):
+ return self._distribution.sample()
+
+
+if __name__ == '__main__':
+ from collections import defaultdict
+ mu = 1.5
+ sigma = 1.22
+ distribution = LogNormalDistribution(mu, sigma, 100)
+ result = defaultdict(int)
+ for i in range(100000):
+ s = int(distribution.sample())
+ if s > 300:
+ continue
+ result[s] += 1
+
+ total = 0
+ for k, v in sorted(result.items(), key=lambda x: x[0]):
+ print("%d\t%.5f" % (k, float(v) / result[1]))
+ total += k * v
+
+ print("Average: %.2f" % (float(total) / sum(result.values()),))
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>
\ No newline at end of file
+<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>
Added: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/calendars-only.plist
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/calendars-only.plist (rev 0)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/calendars-only.plist 2015-08-17 21:14:42 UTC (rev 15048)
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2011-2015 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.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <!-- Define the kinds of software and user behavior the load simulation
+ will simulate. -->
+ <key>clients</key>
+
+ <!-- Have as many different kinds of software and user behavior configurations
+ as you want. Each is a dict -->
+ <array>
+ <dict>
+ <!-- Here is a El Capitan iCal simulator. -->
+ <key>software</key>
+ <string>contrib.performance.loadtest.ical.OS_X_10_11</string>
+
+ <!-- Arguments to use to initialize the client instance. -->
+ <key>params</key>
+ <dict>
+ <!-- Name that appears in logs. -->
+ <key>title</key>
+ <string>10.11</string>
+
+ <!-- Client can poll the calendar home at some interval. This is
+ in seconds. -->
+ <key>calendarHomePollInterval</key>
+ <integer>5</integer>
+
+ <!-- If the server advertises xmpp push, OS X 10.11 can wait for notifications
+ about calendar home changes instead of polling for them periodically. If
+ this option is true, then look for the server advertisement for xmpp push
+ and use it if possible. Still fall back to polling if there is no xmpp push
+ advertised. -->
+ <key>supportPush</key>
+ <false />
+ <key>supportAmpPush</key>
+ <true />
+ </dict>
+
+ <!-- The profiles define certain types of user behavior on top of the
+ client software being simulated. -->
+ <key>profiles</key>
+ <array>
+
+ <!-- First an calendar-creating profile, which will periodically create a new calendar -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.CalendarMaker</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true />
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new calendar. -->
+ <key>interval</key>
+ <integer>15</integer>
+ </dict>
+ </dict>
+
+ <!-- This profile will create a new event, and then periodically change something about the event. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.CalendarUpdater</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true />
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to update an existing calendar. -->
+ <key>interval</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+
+ <!-- This profile randomly shares calendars. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.CalendarSharer</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <false />
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to share an existing calendar. -->
+ <key>interval</key>
+ <integer>30</integer>
+ </dict>
+ </dict>
+
+ <!-- This profile randomly deletes calendars. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.CalendarDeleter</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true />
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to delete an existing calendar. -->
+ <key>interval</key>
+ <integer>30</integer>
+ </dict>
+ </dict>
+ </array>
+
+ <!-- Determine the frequency at which this client configuration will
+ appear in the clients which are created by the load tester. -->
+ <key>weight</key>
+ <integer>1</integer>
+ </dict>
+ </array>
+ </dict>
+</plist>
Added: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/clients-old.plist
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/clients-old.plist (rev 0)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/standard-configs/clients-old.plist 2015-08-17 21:14:42 UTC (rev 15048)
@@ -0,0 +1,527 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2011-2015 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.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <!-- Define the kinds of software and user behavior the load simulation
+ will simulate. -->
+ <key>clients</key>
+
+ <!-- Have as many different kinds of software and user behavior configurations
+ as you want. Each is a dict -->
+ <array>
+
+ <dict>
+
+ <!-- Here is a OS X client simulator. -->
+ <key>software</key>
+ <string>contrib.performance.loadtest.clients.OS_X_10_7</string>
+
+ <!-- Arguments to use to initialize the OS_X_10_7 instance. -->
+ <key>params</key>
+ <dict>
+ <!-- Name that appears in logs. -->
+ <key>title</key>
+ <string>10.7</string>
+
+ <!-- OS_X_10_7 can poll the calendar home at some interval. This is
+ in seconds. -->
+ <key>calendarHomePollInterval</key>
+ <integer>30</integer>
+
+ <!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications
+ about calendar home changes instead of polling for them periodically. If
+ this option is true, then look for the server advertisement for xmpp push
+ and use it if possible. Still fall back to polling if there is no xmpp push
+ advertised. -->
+ <key>supportPush</key>
+ <false />
+
+ <key>supportAmpPush</key>
+ <true/>
+ <key>ampPushHost</key>
+ <string>localhost</string>
+ <key>ampPushPort</key>
+ <integer>62311</integer>
+ </dict>
+
+ <!-- The profiles define certain types of user behavior on top of the
+ client software being simulated. -->
+ <key>profiles</key>
+ <array>
+
+ <!-- First an event-creating profile, which will periodically create
+ new events at a random time on a random calendar. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.Eventer</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new event. -->
+ <key>interval</key>
+ <integer>300</integer>
+
+ <!-- Define how start times (DTSTART) for the randomly generated events
+ will be selected. This is an example of a "Distribution" parameter. The value
+ for most "Distribution" parameters are interchangeable and extensible. -->
+ <key>eventStartDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. It produces timestamps
+ in the near future, limited to certain days of the week and certain hours
+ of the day. -->
+ <key>type</key>
+ <string>contrib.performance.stats.WorkDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- These are the days of the week the distribution will use. -->
+ <key>daysOfWeek</key>
+ <array>
+ <string>mon</string>
+ <string>tue</string>
+ <string>wed</string>
+ <string>thu</string>
+ <string>fri</string>
+ </array>
+
+ <!-- The earliest hour of a day at which an event might be scheduled. -->
+ <key>beginHour</key>
+ <integer>8</integer>
+
+ <!-- And the latest hour of a day (at which an event will be scheduled
+ to begin!). -->
+ <key>endHour</key>
+ <integer>16</integer>
+
+ <!-- The timezone in which the event is scheduled. (XXX Does this
+ really work right?) -->
+ <key>tzname</key>
+ <string>America/Los_Angeles</string>
+ </dict>
+ </dict>
+
+ <!-- Define how recurrences are created. -->
+ <key>recurrenceDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. We have a fixed set of
+ RRULEs defined for this distribution and pick each based on a
+ weight. -->
+ <key>type</key>
+ <string>contrib.performance.stats.RecurrenceDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- False to disable RRULEs -->
+ <key>allowRecurrence</key>
+ <true/>
+
+ <!-- These are the weights for the specific set of RRULEs. -->
+ <key>weights</key>
+ <dict>
+ <!-- Half of all events will be non-recurring -->
+ <key>none</key>
+ <integer>50</integer>
+
+ <!-- Daily and weekly are pretty common -->
+ <key>daily</key>
+ <integer>10</integer>
+ <key>weekly</key>
+ <integer>20</integer>
+
+ <!-- Monthly, yearly, daily & weekly limit not so common -->
+ <key>monthly</key>
+ <integer>2</integer>
+ <key>yearly</key>
+ <integer>1</integer>
+ <key>dailylimit</key>
+ <integer>2</integer>
+ <key>weeklylimit</key>
+ <integer>5</integer>
+
+ <!-- Work days pretty common -->
+ <key>workdays</key>
+ <integer>10</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.EventUpdater</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <false/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new event. -->
+ <key>interval</key>
+ <integer>300</integer>
+
+ <!-- Define how start times (DTSTART) for the randomly generated events
+ will be selected. This is an example of a "Distribution" parameter. The value
+ for most "Distribution" parameters are interchangeable and extensible. -->
+ <key>eventStartDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. It produces timestamps
+ in the near future, limited to certain days of the week and certain hours
+ of the day. -->
+ <key>type</key>
+ <string>contrib.performance.stats.WorkDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- These are the days of the week the distribution will use. -->
+ <key>daysOfWeek</key>
+ <array>
+ <string>mon</string>
+ <string>tue</string>
+ <string>wed</string>
+ <string>thu</string>
+ <string>fri</string>
+ </array>
+
+ <!-- The earliest hour of a day at which an event might be scheduled. -->
+ <key>beginHour</key>
+ <integer>8</integer>
+
+ <!-- And the latest hour of a day (at which an event will be scheduled
+ to begin!). -->
+ <key>endHour</key>
+ <integer>16</integer>
+
+ <!-- The timezone in which the event is scheduled. (XXX Does this
+ really work right?) -->
+ <key>tzname</key>
+ <string>America/Los_Angeles</string>
+ </dict>
+ </dict>
+
+ <!-- Define how recurrences are created. -->
+ <key>recurrenceDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. We have a fixed set of
+ RRULEs defined for this distribution and pick each based on a
+ weight. -->
+ <key>type</key>
+ <string>contrib.performance.stats.RecurrenceDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- False to disable RRULEs -->
+ <key>allowRecurrence</key>
+ <true/>
+
+ <!-- These are the weights for the specific set of RRULEs. -->
+ <key>weights</key>
+ <dict>
+ <!-- Half of all events will be non-recurring -->
+ <key>none</key>
+ <integer>50</integer>
+
+ <!-- Daily and weekly are pretty common -->
+ <key>daily</key>
+ <integer>25</integer>
+ <key>weekly</key>
+ <integer>25</integer>
+
+ <!-- Monthly, yearly, daily & weekly limit not so common -->
+ <key>monthly</key>
+ <integer>0</integer>
+ <key>yearly</key>
+ <integer>0</integer>
+ <key>dailylimit</key>
+ <integer>0</integer>
+ <key>weeklylimit</key>
+ <integer>0</integer>
+
+ <!-- Work days pretty common -->
+ <key>workdays</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- This profile invites some number of new attendees to new events. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.RealisticInviter</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the frequency at which new invitations will be sent out. -->
+ <key>sendInvitationDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>contrib.performance.stats.NormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mu gives the mean of the normal distribution (in seconds). -->
+ <key>mu</key>
+ <integer>60</integer>
+
+ <!-- and sigma gives its standard deviation. -->
+ <key>sigma</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+
+ <!-- Define the distribution of who will be invited to an event.
+
+ When inviteeClumping is turned on each invitee is based on a sample of
+ users "close to" the organizer based on account index. If the clumping
+ is too "tight" for the requested number of attendees, then invites for
+ those larger numbers will simply fail (the sim will report that situation).
+
+ When inviteeClumping is off invitees will be sampled across an entire
+ range of account indexes. In this case the distribution ought to be a
+ UniformIntegerDistribution with min=0 and max set to the number of accounts.
+ -->
+ <key>inviteeDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>contrib.performance.stats.UniformIntegerDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- The minimum value (inclusive) of the uniform distribution. -->
+ <key>min</key>
+ <integer>0</integer>
+ <!-- The maximum value (exclusive) of the uniform distribution. -->
+ <key>max</key>
+ <integer>99</integer>
+ </dict>
+ </dict>
+
+ <key>inviteeClumping</key>
+ <true/>
+
+ <!-- Define the distribution of how many attendees will be invited to an event.
+
+ LogNormal is the best fit to observed data.
+
+
+ For LogNormal "mode" is the peak, "mean" is the mean value. For invites,
+ mode should typically be 1, and mean whatever matches the user behavior.
+ Our typical mean is 6.
+ -->
+ <key>inviteeCountDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>contrib.performance.stats.LogNormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mode - peak-->
+ <key>mode</key>
+ <integer>1</integer>
+ <!-- mean - average-->
+ <key>median</key>
+ <integer>6</integer>
+ <!-- maximum -->
+ <key>maximum</key>
+ <real>60</real>
+ </dict>
+ </dict>
+
+ <!-- Define how start times (DTSTART) for the randomly generated events
+ will be selected. This is an example of a "Distribution" parameter. The value
+ for most "Distribution" parameters are interchangeable and extensible. -->
+ <key>eventStartDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. It produces timestamps
+ in the near future, limited to certain days of the week and certain hours
+ of the day. -->
+ <key>type</key>
+ <string>contrib.performance.stats.WorkDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- These are the days of the week the distribution will use. -->
+ <key>daysOfWeek</key>
+ <array>
+ <string>mon</string>
+ <string>tue</string>
+ <string>wed</string>
+ <string>thu</string>
+ <string>fri</string>
+ </array>
+
+ <!-- The earliest hour of a day at which an event might be scheduled. -->
+ <key>beginHour</key>
+ <integer>8</integer>
+
+ <!-- And the latest hour of a day (at which an event will be scheduled
+ to begin!). -->
+ <key>endHour</key>
+ <integer>16</integer>
+
+ <!-- The timezone in which the event is scheduled. (XXX Does this
+ really work right?) -->
+ <key>tzname</key>
+ <string>America/Los_Angeles</string>
+ </dict>
+ </dict>
+
+ <!-- Define how recurrences are created. -->
+ <key>recurrenceDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. We have a fixed set of
+ RRULEs defined for this distribution and pick each based on a
+ weight. -->
+ <key>type</key>
+ <string>contrib.performance.stats.RecurrenceDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- False to disable RRULEs -->
+ <key>allowRecurrence</key>
+ <true/>
+
+ <!-- These are the weights for the specific set of RRULEs. -->
+ <key>weights</key>
+ <dict>
+ <!-- Half of all events will be non-recurring -->
+ <key>none</key>
+ <integer>50</integer>
+
+ <!-- Daily and weekly are pretty common -->
+ <key>daily</key>
+ <integer>10</integer>
+ <key>weekly</key>
+ <integer>20</integer>
+
+ <!-- Monthly, yearly, daily & weekly limit not so common -->
+ <key>monthly</key>
+ <integer>2</integer>
+ <key>yearly</key>
+ <integer>1</integer>
+ <key>dailylimit</key>
+ <integer>2</integer>
+ <key>weeklylimit</key>
+ <integer>5</integer>
+
+ <!-- Work days pretty common -->
+ <key>workdays</key>
+ <integer>10</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- This profile accepts invitations to events, handles cancels, and
+ handles replies received. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.Accepter</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define how long to wait after seeing a new invitation before
+ accepting it.
+
+ For LogNormal "mode" is the peak, "median" is the 50% cummulative value
+ (i.e., half of the user have accepted by that time).
+ -->
+ <key>acceptDelayDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>contrib.performance.stats.LogNormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mode - peak-->
+ <key>mode</key>
+ <integer>300</integer>
+ <!-- median - 50% done-->
+ <key>median</key>
+ <integer>1800</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- A task-creating profile, which will periodically create
+ new tasks at a random time on a random calendar. -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.Tasker</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new task. -->
+ <key>interval</key>
+ <integer>300</integer>
+ </dict>
+ </dict>
+
+ <!-- A task-updating profile, which will periodically create
+ new tasks at a random time on a random calendar and then
+ update them in some way -->
+ <dict>
+ <key>class</key>
+ <string>contrib.performance.loadtest.profiles.TaskUpdater</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new task. -->
+ <key>interval</key>
+ <integer>60</integer>
+ </dict>
+ </dict>
+
+ </array>
+
+ <!-- Determine the frequency at which this client configuration will
+ appear in the clients which are created by the load tester. -->
+ <key>weight</key>
+ <integer>1</integer>
+ </dict>
+ </array>
+ </dict>
+</plist>
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_ical.py 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_ical.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,2022 +0,0 @@
-##
-# Copyright (c) 2010-2015 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.
-#
-##
-
-from caldavclientlibrary.protocol.caldav.definitions import caldavxml
-from caldavclientlibrary.protocol.caldav.definitions import csxml
-from caldavclientlibrary.protocol.url import URL
-from caldavclientlibrary.protocol.webdav.definitions import davxml
-
-from contrib.performance.httpclient import MemoryConsumer, StringProducer
-from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, OS_X_10_6
-from contrib.performance.loadtest.sim import _DirectoryRecord
-
-from pycalendar.datetime import DateTime
-from pycalendar.timezone import Timezone
-
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
-from twisted.internet.protocol import ProtocolToConsumerAdapter
-from twisted.python.failure import Failure
-from twisted.trial.unittest import TestCase
-from twisted.web.client import ResponseDone
-from twisted.web.http import OK, NO_CONTENT, CREATED, MULTI_STATUS
-from twisted.web.http_headers import Headers
-
-from twistedcaldav.ical import Component
-from twistedcaldav.timezones import TimezoneCache
-
-import json
-import os
-
-EVENT_UID = 'D94F247D-7433-43AF-B84B-ADD684D023B0'
-
-EVENT = """\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-CREATED:20101018T155454Z
-UID:%(UID)s
-DTEND;TZID=America/New_York:20101028T130000
-ATTENDEE;CN="User 03";CUTYPE=INDIVIDUAL;EMAIL="user03 at example.com";PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03 at example.co
- m
-ATTENDEE;CN="User 01";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:user01@
- example.com
-TRANSP:OPAQUE
-SUMMARY:Attended Event
-DTSTART;TZID=America/New_York:20101028T120000
-DTSTAMP:20101018T155513Z
-ORGANIZER;CN="User 01":mailto:user01 at example.com
-SEQUENCE:3
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n") % {'UID': EVENT_UID}
-
-EVENT_INVITE = """\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-X-LIC-LOCATION:America/New_York
-BEGIN:STANDARD
-DTSTART:18831118T120358
-RDATE:18831118T120358
-TZNAME:EST
-TZOFFSETFROM:-045602
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19180331T020000
-RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19181027T020000
-RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19200101T000000
-RDATE:19200101T000000
-RDATE:19420101T000000
-RDATE:19460101T000000
-RDATE:19670101T000000
-TZNAME:EST
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19200328T020000
-RDATE:19200328T020000
-RDATE:19740106T020000
-RDATE:19750223T020000
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19201031T020000
-RDATE:19201031T020000
-RDATE:19450930T020000
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19210424T020000
-RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19210925T020000
-RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19420209T020000
-RDATE:19420209T020000
-TZNAME:EWT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19450814T190000
-RDATE:19450814T190000
-TZNAME:EPT
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19460428T020000
-RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19460929T020000
-RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19551030T020000
-RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19670430T020000
-RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19671029T020000
-RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19760425T020000
-RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19870405T020000
-RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20071104T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-CREATED:20101018T155454Z
-UID:%(UID)s
-DTEND;TZID=America/New_York:20101028T130000
-ATTENDEE;CN="User 02";CUTYPE=INDIVIDUAL;EMAIL="user02 at example.com";PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user02 at example.co
- m
-ATTENDEE;CN="User 03";CUTYPE=INDIVIDUAL;EMAIL="user03 at example.com";PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03 at example.co
- m
-ATTENDEE;CN="User 01";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:user01
-TRANSP:OPAQUE
-SUMMARY:Attended Event
-DTSTART;TZID=America/New_York:20101028T120000
-DTSTAMP:20101018T155513Z
-ORGANIZER;CN="User 01":urn:uuid:user01
-SEQUENCE:3
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n") % {'UID': EVENT_UID}
-
-EVENT_AND_TIMEZONE = """\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-X-LIC-LOCATION:America/New_York
-BEGIN:STANDARD
-DTSTART:18831118T120358
-RDATE:18831118T120358
-TZNAME:EST
-TZOFFSETFROM:-045602
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19180331T020000
-RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19181027T020000
-RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19200101T000000
-RDATE:19200101T000000
-RDATE:19420101T000000
-RDATE:19460101T000000
-RDATE:19670101T000000
-TZNAME:EST
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19200328T020000
-RDATE:19200328T020000
-RDATE:19740106T020000
-RDATE:19750223T020000
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19201031T020000
-RDATE:19201031T020000
-RDATE:19450930T020000
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19210424T020000
-RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19210925T020000
-RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19420209T020000
-RDATE:19420209T020000
-TZNAME:EWT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19450814T190000
-RDATE:19450814T190000
-TZNAME:EPT
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19460428T020000
-RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19460929T020000
-RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19551030T020000
-RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19670430T020000
-RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19671029T020000
-RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19760425T020000
-RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19870405T020000
-RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20071104T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-CREATED:20101018T155454Z
-UID:%(UID)s
-DTEND;TZID=America/New_York:20101028T130000
-ATTENDEE;CN="User 03";CUTYPE=INDIVIDUAL;EMAIL="user03 at example.com";PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03 at example.co
- m
-ATTENDEE;CN="User 01";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:user01@
- example.com
-TRANSP:OPAQUE
-SUMMARY:Attended Event
-DTSTART;TZID=America/New_York:20101028T120000
-DTSTAMP:20101018T155513Z
-ORGANIZER;CN="User 01":mailto:user01 at example.com
-SEQUENCE:3
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n") % {'UID': EVENT_UID}
-
-
-
-class EventTests(TestCase):
- """
- Tests for L{Event}.
- """
- def test_uid(self):
- """
- When the C{vevent} attribute of an L{Event} instance is set,
- L{Event.getUID} returns the UID value from it.
- """
- event = Event(None, u'/foo/bar', u'etag', Component.fromString(EVENT))
- self.assertEquals(event.getUID(), EVENT_UID)
-
-
- def test_withoutUID(self):
- """
- When an L{Event} has a C{vevent} attribute set to C{None},
- L{Event.getUID} returns C{None}.
- """
- event = Event(None, u'/bar/baz', u'etag')
- self.assertIdentical(event.getUID(), None)
-
-
-
-PRINCIPAL_PROPFIND_RESPONSE = """\
-<?xml version='1.0' encoding='UTF-8'?>
-<multistatus xmlns='DAV:'>
- <response>
- <href>/principals/__uids__/user01/</href>
- <propstat>
- <prop>
- <principal-collection-set>
- <href>/principals/</href>
- </principal-collection-set>
- <calendar-home-set xmlns='urn:ietf:params:xml:ns:caldav'>
- <href xmlns='DAV:'>/calendars/__uids__/user01</href>
- </calendar-home-set>
- <calendar-user-address-set xmlns='urn:ietf:params:xml:ns:caldav'>
- <href xmlns='DAV:'>/principals/__uids__/user01/</href>
- <href xmlns='DAV:'>/principals/users/user01/</href>
- </calendar-user-address-set>
- <schedule-inbox-URL xmlns='urn:ietf:params:xml:ns:caldav'>
- <href xmlns='DAV:'>/calendars/__uids__/user01/inbox/</href>
- </schedule-inbox-URL>
- <schedule-outbox-URL xmlns='urn:ietf:params:xml:ns:caldav'>
- <href xmlns='DAV:'>/calendars/__uids__/user01/outbox/</href>
- </schedule-outbox-URL>
- <dropbox-home-URL xmlns='http://calendarserver.org/ns/'>
- <href xmlns='DAV:'>/calendars/__uids__/user01/dropbox/</href>
- </dropbox-home-URL>
- <notification-URL xmlns='http://calendarserver.org/ns/'>
- <href xmlns='DAV:'>/calendars/__uids__/user01/notification/</href>
- </notification-URL>
- <displayname>User 01</displayname>
- <principal-URL>
- <href>/principals/__uids__/user01/</href>
- </principal-URL>
- <supported-report-set>
- <supported-report>
- <report>
- <acl-principal-prop-set/>
- </report>
- </supported-report>
- <supported-report>
- <report>
- <principal-match/>
- </report>
- </supported-report>
- <supported-report>
- <report>
- <principal-property-search/>
- </report>
- </supported-report>
- <supported-report>
- <report>
- <expand-property/>
- </report>
- </supported-report>
- </supported-report-set>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
-</multistatus>
-"""
-
-_CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE = """\
-<?xml version='1.0' encoding='UTF-8'?>
-<multistatus xmlns='DAV:'>
- <response>
- <href>/calendars/__uids__/user01/</href>
- <propstat>
- <prop>
- %(xmpp)s
- <displayname>User 01</displayname>
- <resourcetype>
- <collection/>
- </resourcetype>
- <owner>
- <href>/principals/__uids__/user01/</href>
- </owner>
- <quota-available-bytes>104855434</quota-available-bytes>
- <quota-used-bytes>2166</quota-used-bytes>
- <current-user-privilege-set>
- <privilege>
- <all/>
- </privilege>
- <privilege>
- <read/>
- </privilege>
- <privilege>
- <read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <write/>
- </privilege>
- <privilege>
- <write-properties/>
- </privilege>
- <privilege>
- <write-content/>
- </privilege>
- <privilege>
- <bind/>
- </privilege>
- <privilege>
- <unbind/>
- </privilege>
- <privilege>
- <unlock/>
- </privilege>
- <privilege>
- <read-acl/>
- </privilege>
- <privilege>
- <write-acl/>
- </privilege>
- <privilege>
- <read-current-user-privilege-set/>
- </privilege>
- </current-user-privilege-set>
- <push-transports xmlns='http://calendarserver.org/ns/'/>
- <pushkey xmlns='http://calendarserver.org/ns/'>/Some/Unique/Value</pushkey>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <getctag xmlns='http://calendarserver.org/ns/'/>
- <calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-color xmlns='http://apple.com/ns/ical/'/>
- <calendar-order xmlns='http://apple.com/ns/ical/'/>
- <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/>
- <source xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/>
- <refreshrate xmlns='http://apple.com/ns/ical/'/>
- <publish-url xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
- <response>
- <href>/calendars/__uids__/user01/notification/</href>
- <propstat>
- <prop>
- <displayname>notification</displayname>
- <resourcetype>
- <collection/>
- <notification xmlns='http://calendarserver.org/ns/'/>
- </resourcetype>
- <owner>
- <href>/principals/__uids__/user01/</href>
- </owner>
- <quota-available-bytes>104855434</quota-available-bytes>
- <quota-used-bytes>2166</quota-used-bytes>
- <current-user-privilege-set>
- <privilege>
- <all/>
- </privilege>
- <privilege>
- <read/>
- </privilege>
- <privilege>
- <read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <write/>
- </privilege>
- <privilege>
- <write-properties/>
- </privilege>
- <privilege>
- <write-content/>
- </privilege>
- <privilege>
- <bind/>
- </privilege>
- <privilege>
- <unbind/>
- </privilege>
- <privilege>
- <unlock/>
- </privilege>
- <privilege>
- <read-acl/>
- </privilege>
- <privilege>
- <write-acl/>
- </privilege>
- <privilege>
- <read-current-user-privilege-set/>
- </privilege>
- </current-user-privilege-set>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <xmpp-server xmlns='http://calendarserver.org/ns/'/>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>
- <getctag xmlns='http://calendarserver.org/ns/'/>
- <calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-color xmlns='http://apple.com/ns/ical/'/>
- <calendar-order xmlns='http://apple.com/ns/ical/'/>
- <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/>
- <source xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/>
- <refreshrate xmlns='http://apple.com/ns/ical/'/>
- <push-transports xmlns='http://calendarserver.org/ns/'/>
- <pushkey xmlns='http://calendarserver.org/ns/'/>
- <publish-url xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
- <response>
- <href>/calendars/__uids__/user01/dropbox/</href>
- <propstat>
- <prop>
- <resourcetype>
- <collection/>
- <dropbox-home xmlns='http://calendarserver.org/ns/'/>
- </resourcetype>
- <owner>
- <href>/principals/__uids__/user01/</href>
- </owner>
- <quota-available-bytes>104855434</quota-available-bytes>
- <quota-used-bytes>2166</quota-used-bytes>
- <current-user-privilege-set>
- <privilege>
- <all/>
- </privilege>
- <privilege>
- <read/>
- </privilege>
- <privilege>
- <read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <write/>
- </privilege>
- <privilege>
- <write-properties/>
- </privilege>
- <privilege>
- <write-content/>
- </privilege>
- <privilege>
- <bind/>
- </privilege>
- <privilege>
- <unbind/>
- </privilege>
- <privilege>
- <unlock/>
- </privilege>
- <privilege>
- <read-acl/>
- </privilege>
- <privilege>
- <write-acl/>
- </privilege>
- <privilege>
- <read-current-user-privilege-set/>
- </privilege>
- </current-user-privilege-set>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <xmpp-server xmlns='http://calendarserver.org/ns/'/>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>
- <getctag xmlns='http://calendarserver.org/ns/'/>
- <displayname/>
- <calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-color xmlns='http://apple.com/ns/ical/'/>
- <calendar-order xmlns='http://apple.com/ns/ical/'/>
- <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/>
- <source xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/>
- <refreshrate xmlns='http://apple.com/ns/ical/'/>
- <push-transports xmlns='http://calendarserver.org/ns/'/>
- <pushkey xmlns='http://calendarserver.org/ns/'/>
- <publish-url xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
- <response>
- <href>/calendars/__uids__/user01/calendar/</href>
- <propstat>
- <prop>
- <getctag xmlns='http://calendarserver.org/ns/'>c2696540-4c4c-4a31-adaf-c99630776828#3</getctag>
- <displayname>calendar</displayname>
- <calendar-color xmlns='http://apple.com/ns/ical/'>#0252D4FF</calendar-color>
- <calendar-order xmlns='http://apple.com/ns/ical/'>1</calendar-order>
- <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'>
- <comp name='VEVENT'/>
- <comp name='VTODO'/>
- <comp name='VTIMEZONE'/>
- <comp name='VFREEBUSY'/>
- </supported-calendar-component-set>
- <resourcetype>
- <collection/>
- <calendar xmlns='urn:ietf:params:xml:ns:caldav'/>
- </resourcetype>
- <owner>
- <href>/principals/__uids__/user01/</href>
- </owner>
- <schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'>
- <opaque/>
- </schedule-calendar-transp>
- <quota-available-bytes>104855434</quota-available-bytes>
- <quota-used-bytes>2166</quota-used-bytes>
- <calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[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
-END:VCALENDAR
-]]></calendar-timezone>
- <current-user-privilege-set>
- <privilege>
- <all/>
- </privilege>
- <privilege>
- <read/>
- </privilege>
- <privilege>
- <read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <write/>
- </privilege>
- <privilege>
- <write-properties/>
- </privilege>
- <privilege>
- <write-content/>
- </privilege>
- <privilege>
- <bind/>
- </privilege>
- <privilege>
- <unbind/>
- </privilege>
- <privilege>
- <unlock/>
- </privilege>
- <privilege>
- <read-acl/>
- </privilege>
- <privilege>
- <write-acl/>
- </privilege>
- <privilege>
- <read-current-user-privilege-set/>
- </privilege>
- </current-user-privilege-set>
- <pushkey xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <xmpp-server xmlns='http://calendarserver.org/ns/'/>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>
- <calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/>
- <source xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/>
- <refreshrate xmlns='http://apple.com/ns/ical/'/>
- <push-transports xmlns='http://calendarserver.org/ns/'/>
- <publish-url xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
- <response>
- <href>/calendars/__uids__/user01/outbox/</href>
- <propstat>
- <prop>
- <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'>
- <comp name='VEVENT'/>
- <comp name='VTODO'/>
- <comp name='VTIMEZONE'/>
- <comp name='VFREEBUSY'/>
- </supported-calendar-component-set>
- <resourcetype>
- <collection/>
- <schedule-outbox xmlns='urn:ietf:params:xml:ns:caldav'/>
- </resourcetype>
- <owner>
- <href>/principals/__uids__/user01/</href>
- </owner>
- <quota-available-bytes>104855434</quota-available-bytes>
- <quota-used-bytes>2166</quota-used-bytes>
- <current-user-privilege-set>
- <privilege>
- <all/>
- </privilege>
- <privilege>
- <read/>
- </privilege>
- <privilege>
- <write/>
- </privilege>
- <privilege>
- <write-properties/>
- </privilege>
- <privilege>
- <write-content/>
- </privilege>
- <privilege>
- <bind/>
- </privilege>
- <privilege>
- <unbind/>
- </privilege>
- <privilege>
- <unlock/>
- </privilege>
- <privilege>
- <read-acl/>
- </privilege>
- <privilege>
- <write-acl/>
- </privilege>
- <privilege>
- <read-current-user-privilege-set/>
- </privilege>
- <privilege>
- <schedule-send xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <schedule xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- </current-user-privilege-set>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <xmpp-server xmlns='http://calendarserver.org/ns/'/>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>
- <getctag xmlns='http://calendarserver.org/ns/'/>
- <displayname/>
- <calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-color xmlns='http://apple.com/ns/ical/'/>
- <calendar-order xmlns='http://apple.com/ns/ical/'/>
- <calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/>
- <source xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/>
- <refreshrate xmlns='http://apple.com/ns/ical/'/>
- <push-transports xmlns='http://calendarserver.org/ns/'/>
- <pushkey xmlns='http://calendarserver.org/ns/'/>
- <publish-url xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
- <response>
- <href>/calendars/__uids__/user01/freebusy</href>
- <propstat>
- <prop>
- <resourcetype>
- <free-busy-url xmlns='http://calendarserver.org/ns/'/>
- </resourcetype>
- <owner>
- <href>/principals/__uids__/user01/</href>
- </owner>
- <quota-available-bytes>104855434</quota-available-bytes>
- <quota-used-bytes>2166</quota-used-bytes>
- <current-user-privilege-set>
- <privilege>
- <read/>
- </privilege>
- <privilege>
- <schedule-deliver xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <schedule xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <all/>
- </privilege>
- <privilege>
- <write/>
- </privilege>
- <privilege>
- <write-properties/>
- </privilege>
- <privilege>
- <write-content/>
- </privilege>
- <privilege>
- <bind/>
- </privilege>
- <privilege>
- <unbind/>
- </privilege>
- <privilege>
- <unlock/>
- </privilege>
- <privilege>
- <read-acl/>
- </privilege>
- <privilege>
- <write-acl/>
- </privilege>
- <privilege>
- <read-current-user-privilege-set/>
- </privilege>
- <privilege>
- <read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- </current-user-privilege-set>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <xmpp-server xmlns='http://calendarserver.org/ns/'/>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>
- <getctag xmlns='http://calendarserver.org/ns/'/>
- <displayname/>
- <calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-color xmlns='http://apple.com/ns/ical/'/>
- <calendar-order xmlns='http://apple.com/ns/ical/'/>
- <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/>
- <schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/>
- <source xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/>
- <refreshrate xmlns='http://apple.com/ns/ical/'/>
- <push-transports xmlns='http://calendarserver.org/ns/'/>
- <pushkey xmlns='http://calendarserver.org/ns/'/>
- <publish-url xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
- <response>
- <href>/calendars/__uids__/user01/inbox/</href>
- <propstat>
- <prop>
- <getctag xmlns='http://calendarserver.org/ns/'>a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0</getctag>
- <displayname>inbox</displayname>
- <supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'>
- <comp name='VEVENT'/>
- <comp name='VTODO'/>
- <comp name='VTIMEZONE'/>
- <comp name='VFREEBUSY'/>
- </supported-calendar-component-set>
- <resourcetype>
- <collection/>
- <schedule-inbox xmlns='urn:ietf:params:xml:ns:caldav'/>
- </resourcetype>
- <owner>
- <href>/principals/__uids__/user01/</href>
- </owner>
- <calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>
- <href xmlns='DAV:'>/calendars/__uids__/user01/calendar</href>
- </calendar-free-busy-set>
- <schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'>
- <href xmlns='DAV:'>/calendars/__uids__/user01/calendar</href>
- </schedule-default-calendar-URL>
- <quota-available-bytes>104855434</quota-available-bytes>
- <quota-used-bytes>2166</quota-used-bytes>
- <current-user-privilege-set>
- <privilege>
- <schedule-deliver xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <schedule xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- <privilege>
- <all/>
- </privilege>
- <privilege>
- <read/>
- </privilege>
- <privilege>
- <write/>
- </privilege>
- <privilege>
- <write-properties/>
- </privilege>
- <privilege>
- <write-content/>
- </privilege>
- <privilege>
- <bind/>
- </privilege>
- <privilege>
- <unbind/>
- </privilege>
- <privilege>
- <unlock/>
- </privilege>
- <privilege>
- <read-acl/>
- </privilege>
- <privilege>
- <write-acl/>
- </privilege>
- <privilege>
- <read-current-user-privilege-set/>
- </privilege>
- <privilege>
- <read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/>
- </privilege>
- </current-user-privilege-set>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- <xmpp-server xmlns='http://calendarserver.org/ns/'/>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>
- <calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-color xmlns='http://apple.com/ns/ical/'/>
- <calendar-order xmlns='http://apple.com/ns/ical/'/>
- <schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/>
- <calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/>
- <source xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/>
- <subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/>
- <refreshrate xmlns='http://apple.com/ns/ical/'/>
- <push-transports xmlns='http://calendarserver.org/ns/'/>
- <pushkey xmlns='http://calendarserver.org/ns/'/>
- <publish-url xmlns='http://calendarserver.org/ns/'/>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
-</multistatus>
-"""
-
-CALENDAR_HOME_PROPFIND_RESPONSE = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {
- "xmpp": """\
- <xmpp-server xmlns='http://calendarserver.org/ns/'/>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'/>""",
-}
-
-CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {
- "xmpp": """\
- <xmpp-server xmlns='http://calendarserver.org/ns/'>xmpp.example.invalid:1952</xmpp-server>
- <xmpp-uri xmlns='http://calendarserver.org/ns/'>xmpp:pubsub.xmpp.example.invalid?pubsub;node=/CalDAV/another.example.invalid/user01/</xmpp-uri>""",
-}
-
-CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {"xmpp": ""}
-
-
-
-class MemoryResponse(object):
- def __init__(self, version, code, phrase, headers, bodyProducer):
- self.version = version
- self.code = code
- self.phrase = phrase
- self.headers = headers
- self.length = bodyProducer.length
- self._bodyProducer = bodyProducer
-
-
- def deliverBody(self, protocol):
- protocol.makeConnection(self._bodyProducer)
- d = self._bodyProducer.startProducing(ProtocolToConsumerAdapter(protocol))
- d.addCallback(lambda ignored: protocol.connectionLost(Failure(ResponseDone())))
-
-
-
-class OS_X_10_6Mixin:
- """
- Mixin for L{TestCase}s for L{OS_X_10_6}.
- """
- def setUp(self):
- TimezoneCache.create()
- self.record = _DirectoryRecord(
- u"user91", u"user91", u"User 91", u"user91 at example.org", u"user91",
- )
- serializePath = self.mktemp()
- os.mkdir(serializePath)
- self.client = OS_X_10_6(
- None,
- "http://127.0.0.1",
- "/principals/users/%s/",
- serializePath,
- self.record,
- None,
- )
-
-
- def interceptRequests(self):
- requests = []
- def request(*args, **kwargs):
- result = Deferred()
- requests.append((result, args))
- return result
- self.client._request = request
- return requests
-
-
-
-class OS_X_10_6Tests(OS_X_10_6Mixin, TestCase):
- """
- Tests for L{OS_X_10_6}.
- """
- def test_parsePrincipalPROPFINDResponse(self):
- """
- L{Principal._parsePROPFINDResponse} accepts an XML document
- like the one in the response to a I{PROPFIND} request for
- I{/principals/__uids__/<uid>/} and returns a C{PropFindResult}
- representing the data from it.
- """
- principals = self.client._parseMultiStatus(PRINCIPAL_PROPFIND_RESPONSE)
- principal = principals['/principals/__uids__/user01/']
- self.assertEquals(
- principal.getHrefProperties(),
- {
- davxml.principal_collection_set: URL(path='/principals/'),
- caldavxml.calendar_home_set: URL(path='/calendars/__uids__/user01'),
- caldavxml.calendar_user_address_set: (
- URL(path='/principals/__uids__/user01/'),
- URL(path='/principals/users/user01/'),
- ),
- caldavxml.schedule_inbox_URL: URL(path='/calendars/__uids__/user01/inbox/'),
- caldavxml.schedule_outbox_URL: URL(path='/calendars/__uids__/user01/outbox/'),
- csxml.dropbox_home_URL: URL(path='/calendars/__uids__/user01/dropbox/'),
- csxml.notification_URL: URL(path='/calendars/__uids__/user01/notification/'),
- davxml.principal_URL: URL(path='/principals/__uids__/user01/'),
- }
- )
- self.assertEquals(
- principal.getTextProperties(),
- {davxml.displayname: 'User 01'})
-
-# self.assertEquals(
-# principal.getSomething(),
-# {SUPPORTED_REPORT_SET: (
-# '{DAV:}acl-principal-prop-set',
-# '{DAV:}principal-match',
-# '{DAV:}principal-property-search',
-# '{DAV:}expand-property',
-# )})
-
-
- def test_extractCalendars(self):
- """
- L{OS_X_10_6._extractCalendars} accepts a calendar home
- PROPFIND response body and returns a list of calendar objects
- constructed from the data extracted from the response.
- """
- home = "/calendars/__uids__/user01/"
- calendars = self.client._extractCalendars(
- self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE), home)
- calendars.sort(key=lambda cal: cal.resourceType)
- calendar, inbox = calendars
-
- self.assertEquals(calendar.resourceType, caldavxml.calendar)
- self.assertEquals(calendar.name, "calendar")
- self.assertEquals(calendar.url, "/calendars/__uids__/user01/calendar/")
- self.assertEquals(calendar.changeToken, "c2696540-4c4c-4a31-adaf-c99630776828#3")
-
- self.assertEquals(inbox.resourceType, caldavxml.schedule_inbox)
- self.assertEquals(inbox.name, "inbox")
- self.assertEquals(inbox.url, "/calendars/__uids__/user01/inbox/")
- self.assertEquals(inbox.changeToken, "a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0")
-
- self.assertEqual({}, self.client.xmpp)
-
-
- def test_extractCalendarsXMPP(self):
- """
- If there is XMPP push information in a calendar home PROPFIND response,
- L{OS_X_10_6._extractCalendars} finds it and records it.
- """
- home = "/calendars/__uids__/user01/"
- self.client._extractCalendars(
- self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP),
- home
- )
- self.assertEqual({
- home: XMPPPush(
- "xmpp.example.invalid:1952",
- "xmpp:pubsub.xmpp.example.invalid?pubsub;node=/CalDAV/another.example.invalid/user01/",
- "/Some/Unique/Value"
- )},
- self.client.xmpp
- )
-
-
- def test_handleMissingXMPP(self):
- home = "/calendars/__uids__/user01/"
- self.client._extractCalendars(
- self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING), home)
- self.assertEqual({}, self.client.xmpp)
-
-
- @inlineCallbacks
- def test_changeEventAttendee(self):
- """
- OS_X_10_6.changeEventAttendee removes one attendee from an
- existing event and appends another.
- """
- requests = self.interceptRequests()
-
- vevent = Component.fromString(EVENT)
- attendees = tuple(vevent.mainComponent().properties("ATTENDEE"))
- old = attendees[0]
- new = old.duplicate()
- new.setParameter('CN', 'Some Other Guy')
- event = Event(self.client.serializeLocation(), u'/some/calendar/1234.ics', None, vevent)
- self.client._events[event.url] = event
- self.client.changeEventAttendee(event.url, old, new)
-
- _ignore_result, req = requests.pop(0)
-
- # iCal PUTs the new VCALENDAR object.
- _ignore_expectedResponseCode, method, url, headers, body = req
- self.assertEquals(method, 'PUT')
- self.assertEquals(url, 'http://127.0.0.1' + event.url)
- self.assertIsInstance(url, str)
- self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
-
- consumer = MemoryConsumer()
- yield body.startProducing(consumer)
- vevent = Component.fromString(consumer.value())
- attendees = tuple(vevent.mainComponent().properties("ATTENDEE"))
- self.assertEquals(len(attendees), 2)
- self.assertEquals(attendees[0].parameterValue('CN'), 'User 01')
- self.assertEquals(attendees[1].parameterValue('CN'), 'Some Other Guy')
-
-
- def test_addEvent(self):
- """
- L{OS_X_10_6.addEvent} PUTs the event passed to it to the
- server and updates local state to reflect its existence.
- """
- requests = self.interceptRequests()
-
- calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/mumble/', None)
- self.client._calendars[calendar.url] = calendar
-
- vcalendar = Component.fromString(EVENT)
- d = self.client.addEvent(u'/mumble/frotz.ics', vcalendar)
-
- result, req = requests.pop(0)
-
- # iCal PUTs the new VCALENDAR object.
- expectedResponseCode, method, url, headers, body = req
- self.assertEqual(expectedResponseCode, CREATED)
- self.assertEqual(method, 'PUT')
- self.assertEqual(url, 'http://127.0.0.1/mumble/frotz.ics')
- self.assertIsInstance(url, str)
- self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
- consumer = MemoryConsumer()
- finished = body.startProducing(consumer)
- def cbFinished(ignored):
- self.assertEqual(
- Component.fromString(consumer.value()),
- Component.fromString(EVENT_AND_TIMEZONE))
- finished.addCallback(cbFinished)
-
- def requested(ignored):
- response = MemoryResponse(
- ('HTTP', '1', '1'), CREATED, "Created", Headers({}),
- StringProducer(""))
- result.callback(response)
- finished.addCallback(requested)
-
- return d
-
-
- @inlineCallbacks
- def test_addInvite(self):
- """
- L{OS_X_10_6.addInvite} PUTs the event passed to it to the
- server and updates local state to reflect its existence, but
- it also does attendee auto-complete and free-busy checks before
- the PUT.
- """
-
- calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/mumble/', None)
- self.client._calendars[calendar.url] = calendar
-
- vcalendar = Component.fromString(EVENT_INVITE)
-
- self.client.uuid = u'urn:uuid:user01'
- self.client.email = u'mailto:user01 at example.com'
- self.client.principalCollection = "/principals/"
- self.client.outbox = "/calendars/__uids__/user01/outbox/"
-
- @inlineCallbacks
- def _testReport(*args, **kwargs):
- expectedResponseCode, method, url, headers, body = args
- self.assertEqual(expectedResponseCode, (MULTI_STATUS,))
- self.assertEqual(method, 'REPORT')
- self.assertEqual(url, 'http://127.0.0.1/principals/')
- self.assertIsInstance(url, str)
- self.assertEqual(headers.getRawHeaders('content-type'), ['text/xml'])
-
- consumer = MemoryConsumer()
- yield body.startProducing(consumer)
-
- response = MemoryResponse(
- ('HTTP', '1', '1'), MULTI_STATUS, "MultiStatus", Headers({}),
- StringProducer("<?xml version='1.0' encoding='UTF-8'?><multistatus xmlns='DAV:' />"))
-
- returnValue(response)
-
- @inlineCallbacks
- def _testPost(*args, **kwargs):
- expectedResponseCode, method, url, headers, body = args
- self.assertEqual(expectedResponseCode, OK)
- self.assertEqual(method, 'POST')
- self.assertEqual(url, 'http://127.0.0.1/calendars/__uids__/user01/outbox/')
- self.assertIsInstance(url, str)
- self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
- consumer = MemoryConsumer()
- yield body.startProducing(consumer)
- self.assertNotEqual(consumer.value().find(kwargs["attendee"]), -1)
-
- response = MemoryResponse(
- ('HTTP', '1', '1'), OK, "OK", Headers({}),
- StringProducer(""))
-
- returnValue(response)
-
- def _testPost02(*args, **kwargs):
- return _testPost(*args, attendee="ATTENDEE:mailto:user02 at example.com", **kwargs)
-
- def _testPost03(*args, **kwargs):
- return _testPost(*args, attendee="ATTENDEE:mailto:user03 at example.com", **kwargs)
-
- @inlineCallbacks
- def _testPut(*args, **kwargs):
- expectedResponseCode, method, url, headers, body = args
- self.assertEqual(expectedResponseCode, CREATED)
- self.assertEqual(method, 'PUT')
- self.assertEqual(url, 'http://127.0.0.1/mumble/frotz.ics')
- self.assertIsInstance(url, str)
- self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
- consumer = MemoryConsumer()
- yield body.startProducing(consumer)
- self.assertEqual(
- Component.fromString(consumer.value()),
- Component.fromString(EVENT_INVITE))
-
- response = MemoryResponse(
- ('HTTP', '1', '1'), CREATED, "Created", Headers({}),
- StringProducer(""))
-
- returnValue(response)
-
- requests = [_testReport, _testPost02, _testReport, _testPost03, _testPut, ]
-
- def _requestHandler(*args, **kwargs):
- handler = requests.pop(0)
- return handler(*args, **kwargs)
- self.client._request = _requestHandler
- yield self.client.addInvite('/mumble/frotz.ics', vcalendar)
-
-
- def test_deleteEvent(self):
- """
- L{OS_X_10_6.deleteEvent} DELETEs the event at the relative
- URL passed to it and updates local state to reflect its
- removal.
- """
- requests = self.interceptRequests()
-
- calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/foo/', None)
- event = Event(None, calendar.url + u'bar.ics', None)
- self.client._calendars[calendar.url] = calendar
- self.client._setEvent(event.url, event)
-
- d = self.client.deleteEvent(event.url)
-
- result, req = requests.pop()
-
- expectedResponseCode, method, url = req
-
- self.assertEqual(expectedResponseCode, NO_CONTENT)
- self.assertEqual(method, 'DELETE')
- self.assertEqual(url, 'http://127.0.0.1' + event.url)
- self.assertIsInstance(url, str)
-
- self.assertNotIn(event.url, self.client._events)
- self.assertNotIn(u'bar.ics', calendar.events)
-
- response = MemoryResponse(
- ('HTTP', '1', '1'), NO_CONTENT, "No Content", None,
- StringProducer(""))
- result.callback(response)
- return d
-
-
- def test_serialization(self):
- """
- L{OS_X_10_6.serialize} properly generates a JSON document.
- """
- clientPath = os.path.join(self.client.serializePath, "user91-OS_X_10.6")
- self.assertFalse(os.path.exists(clientPath))
- indexPath = os.path.join(clientPath, "index.json")
- self.assertFalse(os.path.exists(indexPath))
-
- cal1 = """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:004f8e41-b071-4b30-bb3b-6aada4adcc10
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- cal2 = """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:00a79cad-857b-418e-a54a-340b5686d747
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- events = (
- Event(self.client.serializeLocation(), u'/home/calendar/1.ics', u'123.123', Component.fromString(cal1)),
- Event(self.client.serializeLocation(), u'/home/inbox/i1.ics', u'123.123', Component.fromString(cal2)),
- )
- self.client._events.update(dict([[event.url, event] for event in events]))
-
- calendars = (
- Calendar(str(caldavxml.calendar), set(('VEVENT',)), u'calendar', u'/home/calendar/', "123"),
- Calendar(str(caldavxml.calendar), set(('VTODO',)), u'tasks', u'/home/tasks/', "456"),
- Calendar(str(caldavxml.schedule_inbox), set(('VEVENT', "VTODO",)), u'calendar', u'/home/inbox/', "789"),
- )
- self.client._calendars.update(dict([[calendar.url, calendar] for calendar in calendars]))
- self.client._calendars["/home/calendar/"].events["1.ics"] = events[0]
- self.client._calendars["/home/inbox/"].events["i1.ics"] = events[1]
-
- self.client.serialize()
- self.assertTrue(os.path.exists(clientPath))
- self.assertTrue(os.path.exists(indexPath))
- def _normDict(d):
- return dict([(k, sorted(v, key=lambda x: x["changeToken" if k == "calendars" else "url"]) if v else None,) for k, v in d.items()])
- self.assertEqual(_normDict(json.loads(open(indexPath).read())), _normDict(json.loads("""{
- "calendars": [
- {
- "changeToken": "123",
- "name": "calendar",
- "resourceType": "{urn:ietf:params:xml:ns:caldav}calendar",
- "componentTypes": [
- "VEVENT"
- ],
- "url": "/home/calendar/",
- "events": [
- "1.ics"
- ]
- },
- {
- "changeToken": "789",
- "name": "calendar",
- "resourceType": "{urn:ietf:params:xml:ns:caldav}schedule-inbox",
- "componentTypes": [
- "VEVENT",
- "VTODO"
- ],
- "url": "/home/inbox/",
- "events": [
- "i1.ics"
- ]
- },
- {
- "changeToken": "456",
- "name": "tasks",
- "resourceType": "{urn:ietf:params:xml:ns:caldav}calendar",
- "componentTypes": [
- "VTODO"
- ],
- "url": "/home/tasks/",
- "events": []
- }
- ],
- "principalURL": null,
- "events": [
- {
- "url": "/home/calendar/1.ics",
- "scheduleTag": null,
- "etag": "123.123",
- "uid": "004f8e41-b071-4b30-bb3b-6aada4adcc10"
- },
- {
- "url": "/home/inbox/i1.ics",
- "scheduleTag": null,
- "etag": "123.123",
- "uid": "00a79cad-857b-418e-a54a-340b5686d747"
- }
- ]
-}""")))
-
- event1Path = os.path.join(clientPath, "calendar", "1.ics")
- self.assertTrue(os.path.exists(event1Path))
- self.assertEqual(open(event1Path).read(), cal1)
-
- event2Path = os.path.join(clientPath, "inbox", "i1.ics")
- self.assertTrue(os.path.exists(event2Path))
- self.assertEqual(open(event2Path).read(), cal2)
-
-
- def test_deserialization(self):
- """
- L{OS_X_10_6.deserailize} properly parses a JSON document.
- """
-
- cal1 = """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:004f8e41-b071-4b30-bb3b-6aada4adcc10
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
- cal2 = """BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:00a79cad-857b-418e-a54a-340b5686d747
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
- clientPath = os.path.join(self.client.serializePath, "user91-OS_X_10.6")
- os.mkdir(clientPath)
- indexPath = os.path.join(clientPath, "index.json")
- open(indexPath, "w").write("""{
- "calendars": [
- {
- "changeToken": "321",
- "name": "calendar",
- "resourceType": "{urn:ietf:params:xml:ns:caldav}calendar",
- "componentTypes": [
- "VEVENT"
- ],
- "url": "/home/calendar/",
- "events": [
- "2.ics"
- ]
- },
- {
- "changeToken": "987",
- "name": "calendar",
- "resourceType": "{urn:ietf:params:xml:ns:caldav}schedule-inbox",
- "componentTypes": [
- "VEVENT",
- "VTODO"
- ],
- "url": "/home/inbox/",
- "events": [
- "i2.ics"
- ]
- },
- {
- "changeToken": "654",
- "name": "tasks",
- "resourceType": "{urn:ietf:params:xml:ns:caldav}calendar",
- "componentTypes": [
- "VTODO"
- ],
- "url": "/home/tasks/",
- "events": []
- }
- ],
- "principalURL": null,
- "events": [
- {
- "url": "/home/calendar/2.ics",
- "scheduleTag": null,
- "etag": "321.321",
- "uid": "004f8e41-b071-4b30-bb3b-6aada4adcc10"
- },
- {
- "url": "/home/inbox/i2.ics",
- "scheduleTag": null,
- "etag": "987.987",
- "uid": "00a79cad-857b-418e-a54a-340b5686d747"
- }
- ]
-}""")
-
- os.mkdir(os.path.join(clientPath, "calendar"))
- event1Path = os.path.join(clientPath, "calendar", "2.ics")
- open(event1Path, "w").write(cal1)
- os.mkdir(os.path.join(clientPath, "inbox"))
- event1Path = os.path.join(clientPath, "inbox", "i2.ics")
- open(event1Path, "w").write(cal2)
-
- self.client.deserialize()
-
- self.assertEqual(len(self.client._calendars), 3)
- self.assertTrue("/home/calendar/" in self.client._calendars)
- self.assertEqual(self.client._calendars["/home/calendar/"].changeToken, "321")
- self.assertEqual(self.client._calendars["/home/calendar/"].name, "calendar")
- self.assertEqual(self.client._calendars["/home/calendar/"].resourceType, "{urn:ietf:params:xml:ns:caldav}calendar")
- self.assertEqual(self.client._calendars["/home/calendar/"].componentTypes, set(("VEVENT",)))
- self.assertTrue("/home/tasks/" in self.client._calendars)
- self.assertTrue("/home/inbox/" in self.client._calendars)
- self.assertEqual(self.client._calendars["/home/inbox/"].componentTypes, set(("VEVENT", "VTODO",)))
- self.assertEqual(len(self.client._events), 2)
- self.assertTrue("/home/calendar/2.ics" in self.client._events)
- self.assertEqual(self.client._events["/home/calendar/2.ics"].scheduleTag, None)
- self.assertEqual(self.client._events["/home/calendar/2.ics"].etag, "321.321")
- self.assertEqual(self.client._events["/home/calendar/2.ics"].getUID(), "004f8e41-b071-4b30-bb3b-6aada4adcc10")
- self.assertEqual(str(self.client._events["/home/calendar/2.ics"].component), cal1)
- self.assertTrue("/home/inbox/i2.ics" in self.client._events)
- self.assertEqual(self.client._events["/home/inbox/i2.ics"].scheduleTag, None)
- self.assertEqual(self.client._events["/home/inbox/i2.ics"].etag, "987.987")
- self.assertEqual(self.client._events["/home/inbox/i2.ics"].getUID(), "00a79cad-857b-418e-a54a-340b5686d747")
- self.assertEqual(str(self.client._events["/home/inbox/i2.ics"].component), cal2)
-
-
-
-class UpdateCalendarTests(OS_X_10_6Mixin, TestCase):
- """
- Tests for L{OS_X_10_6._updateCalendar}.
- """
-
- _CALENDAR_PROPFIND_RESPONSE_BODY = """\
-<?xml version='1.0' encoding='UTF-8'?>
-<multistatus xmlns='DAV:'>
- <response>
- <href>/something/anotherthing.ics</href>
- <propstat>
- <prop>
- <resourcetype>
- <collection/>
- </resourcetype>
- <getetag>"None"</getetag>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- <propstat>
- <prop>
- </prop>
- <status>HTTP/1.1 404 Not Found</status>
- </propstat>
- </response>
- <response>
- <href>/something/else.ics</href>
- <propstat>
- <prop>
- <resourcetype>
- <collection/>
- </resourcetype>
- <getetag>"None"</getetag>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- </response>
-</multistatus>
-"""
- _CALENDAR_REPORT_RESPONSE_BODY = """\
-<?xml version='1.0' encoding='UTF-8'?>
-<multistatus xmlns='DAV:'>
- <response>
- <href>/something/anotherthing.ics</href>
- <status>HTTP/1.1 404 Not Found</status>
- </response>
- <response>
- <href>/something/else.ics</href>
- <propstat>
- <prop>
- <getetag>"ef70beb4cb7da4b2e2950350b09e9a01"</getetag>
- <calendar-data xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:CD54161A13AA8A4649D3781E at caldav.corp.apple.com
-DTSTART:20110715T140000Z
-DURATION:PT1H
-DTSTAMP:20110715T144217Z
-SUMMARY:Test2
-END:VEVENT
-END:VCALENDAR
-]]></calendar-data>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- </response>
-</multistatus>
-"""
-
- _CALENDAR_REPORT_RESPONSE_BODY_1 = """\
-<?xml version='1.0' encoding='UTF-8'?>
-<multistatus xmlns='DAV:'>
- <response>
- <href>/something/anotherthing.ics</href>
- <propstat>
- <prop>
- <getetag>"ef70beb4cb7da4b2e2950350b09e9a01"</getetag>
- <calendar-data xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:anotherthing at caldav.corp.apple.com
-DTSTART:20110715T140000Z
-DURATION:PT1H
-DTSTAMP:20110715T144217Z
-SUMMARY:Test1
-END:VEVENT
-END:VCALENDAR
-]]></calendar-data>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- </response>
-</multistatus>
-"""
-
- _CALENDAR_REPORT_RESPONSE_BODY_2 = """\
-<?xml version='1.0' encoding='UTF-8'?>
-<multistatus xmlns='DAV:'>
- <response>
- <href>/something/else.ics</href>
- <propstat>
- <prop>
- <getetag>"ef70beb4cb7da4b2e2950350b09e9a01"</getetag>
- <calendar-data xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:else at caldav.corp.apple.com
-DTSTART:20110715T140000Z
-DURATION:PT1H
-DTSTAMP:20110715T144217Z
-SUMMARY:Test2
-END:VEVENT
-END:VCALENDAR
-]]></calendar-data>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- </response>
-</multistatus>
-"""
-
- def test_eventMissing(self):
- """
- If an event included in the calendar PROPFIND response no longer exists
- by the time a REPORT is issued for that event, the 404 is handled and
- the rest of the normal update logic for that event is skipped.
- """
- requests = self.interceptRequests()
-
- calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
- self.client._calendars[calendar.url] = calendar
- self.client._updateCalendar(calendar, "1234")
- result, req = requests.pop(0)
- expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
- self.assertEqual('PROPFIND', method)
- self.assertEqual('http://127.0.0.1/something/', url)
- self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
- result.callback(
- MemoryResponse(
- ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
- StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
-
- result, req = requests.pop(0)
- expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
- self.assertEqual('REPORT', method)
- self.assertEqual('http://127.0.0.1/something/', url)
- self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
- # Someone else comes along and gets rid of the event
- del self.client._events["/something/anotherthing.ics"]
-
- result.callback(
- MemoryResponse(
- ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
- StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)))
-
- # Verify that processing proceeded to the response after the one with a
- # 404 status.
- self.assertIn('/something/else.ics', self.client._events)
-
-
- def test_multigetBatch(self):
- """
- If an event included in the calendar PROPFIND response no longer exists
- by the time a REPORT is issued for that event, the 404 is handled and
- the rest of the normal update logic for that event is skipped.
- """
- requests = self.interceptRequests()
-
- self.patch(self.client, "MULTIGET_BATCH_SIZE", 1)
-
- calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
- self.client._calendars[calendar.url] = calendar
- self.client._updateCalendar(calendar, "1234")
- result, req = requests.pop(0)
- expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
- self.assertEqual('PROPFIND', method)
- self.assertEqual('http://127.0.0.1/something/', url)
- self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
- result.callback(
- MemoryResponse(
- ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
- StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
-
- result, req = requests.pop(0)
- expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
- self.assertEqual('REPORT', method)
- self.assertEqual('http://127.0.0.1/something/', url)
- self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
- result.callback(
- MemoryResponse(
- ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
- StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_1)))
-
- self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
- self.assertTrue(self.client._events['/something/else.ics'].etag is None)
-
- result, req = requests.pop(0)
- expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
- self.assertEqual('REPORT', method)
- self.assertEqual('http://127.0.0.1/something/', url)
- self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
- result.callback(
- MemoryResponse(
- ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
- StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_2)))
-
- self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
- self.assertTrue(self.client._events['/something/else.ics'].etag is not None)
-
-
-
-class VFreeBusyTests(OS_X_10_6Mixin, TestCase):
- """
- Tests for L{OS_X_10_6.requestAvailability}.
- """
- def test_requestAvailability(self):
- """
- L{OS_X_10_6.requestAvailability} accepts a date range and a set of
- account uuids and issues a VFREEBUSY request. It returns a Deferred
- which fires with a dict mapping account uuids to availability range
- information.
- """
- self.client.uuid = u'urn:uuid:user01'
- self.client.email = u'mailto:user01 at example.com'
- self.client.outbox = "/calendars/__uids__/%s/outbox/" % (self.record.uid,)
- requests = self.interceptRequests()
-
- start = DateTime(2011, 6, 10, 10, 45, 0, tzid=Timezone.UTCTimezone)
- end = DateTime(2011, 6, 10, 11, 15, 0, tzid=Timezone.UTCTimezone)
- d = self.client.requestAvailability(
- start, end, [u"urn:uuid:user05", u"urn:uuid:user10"])
-
- result, req = requests.pop(0)
- expectedResponseCode, method, url, headers, body = req
-
- self.assertEqual(OK, expectedResponseCode)
- self.assertEqual('POST', method)
- self.assertEqual(
- 'http://127.0.0.1/calendars/__uids__/%s/outbox/' % (self.record.uid,),
- url)
-
- self.assertEqual(headers.getRawHeaders('originator'), ['mailto:user01 at example.com'])
- self.assertEqual(headers.getRawHeaders('recipient'), ['urn:uuid:user05, urn:uuid:user10'])
- self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
- consumer = MemoryConsumer()
- finished = body.startProducing(consumer)
- def cbFinished(ignored):
- vevent = Component.fromString(consumer.value())
- uid = vevent.resourceUID()
- dtstamp = vevent.mainComponent().propertyValue("DTSTAMP")
- dtstamp = dtstamp.getText()
- self.assertEqual("""BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VFREEBUSY
-UID:%(uid)s
-DTEND:20110611T000000Z
-ATTENDEE:urn:uuid:user05
-ATTENDEE:urn:uuid:user10
-DTSTART:20110610T000000Z
-DTSTAMP:%(dtstamp)s
-ORGANIZER:mailto:user01 at example.com
-SUMMARY:Availability for urn:uuid:user05, urn:uuid:user10
-END:VFREEBUSY
-END:VCALENDAR
-""".replace('\n', '\r\n') % {'uid': uid, 'dtstamp': dtstamp}, consumer.value())
-
- finished.addCallback(cbFinished)
-
- def requested(ignored):
- response = MemoryResponse(
- ('HTTP', '1', '1'), OK, "Ok", Headers({}),
- StringProducer(""))
- result.callback(response)
- finished.addCallback(requested)
-
- return d
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_population.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_population.py 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_population.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,390 +0,0 @@
-##
-# Copyright (c) 2011-2015 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 some things in L{loadtest.population}.
-"""
-
-from twisted.trial.unittest import TestCase
-
-from contrib.performance.loadtest.population import ReportStatistics
-
-class ReportStatisticsTests(TestCase):
- """
- Tests for L{loadtest.population.ReportStatistics}.
- """
- def test_countUsers(self):
- """
- L{ReportStatistics.countUsers} returns the number of users observed to
- have acted in the simulation.
- """
- logger = ReportStatistics()
- users = ['user01', 'user02', 'user03']
- for user in users:
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=1.23, user=user, client_type="test", client_id="1234"
- ))
- self.assertEqual(len(users), logger.countUsers())
-
-
- def test_countClients(self):
- """
- L{ReportStatistics.countClients} returns the number of clients observed to
- have acted in the simulation.
- """
- logger = ReportStatistics()
- clients = ['c01', 'c02', 'c03']
- for client in clients:
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=1.23, user="user01", client_type="test", client_id=client
- ))
- self.assertEqual(len(clients), logger.countClients())
-
-
- def test_clientFailures(self):
- """
- L{ReportStatistics.countClientFailures} returns the number of clients observed to
- have failed in the simulation.
- """
- logger = ReportStatistics()
- clients = ['c01', 'c02', 'c03']
- for client in clients:
- logger.observe(dict(
- type='client-failure', reason="testing %s" % (client,)
- ))
- self.assertEqual(len(clients), logger.countClientFailures())
-
-
- def test_simFailures(self):
- """
- L{ReportStatistics.countSimFailures} returns the number of clients observed to
- have caused an error in the simulation.
- """
- logger = ReportStatistics()
- clients = ['c01', 'c02', 'c03']
- for client in clients:
- logger.observe(dict(
- type='sim-failure', reason="testing %s" % (client,)
- ))
- self.assertEqual(len(clients), logger.countSimFailures())
-
-
- def test_noFailures(self):
- """
- If fewer than 1% of requests fail, fewer than 1% of requests take 5
- seconds or more, and fewer than 5% of requests take 3 seconds or more,
- L{ReportStatistics.failures} returns an empty list.
- """
- logger = ReportStatistics()
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual([], logger.failures())
-
-
- def test_requestFailures(self):
- """
- If more than 1% of requests fail, L{ReportStatistics.failures} returns a
- list containing a string describing this.
- """
- logger = ReportStatistics()
- for _ignore in range(98):
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='GET', success=False,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- ["Greater than 1% GET failed"],
- logger.failures())
-
-
- def test_threeSecondFailure(self):
- """
- If more than 5% of requests take longer than 3 seconds,
- L{ReportStatistics.failures} returns a list containing a string
- describing that.
- """
- logger = ReportStatistics()
- for _ignore in range(94):
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
- for _ignore in range(5):
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=3.5, user='user02', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- ["Greater than 5% GET exceeded 3 second response time"],
- logger.failures())
-
-
- def test_fiveSecondFailure(self):
- """
- If more than 1% of requests take longer than 5 seconds,
- L{ReportStatistics.failures} returns a list containing a string
- describing that.
- """
- logger = ReportStatistics()
- for _ignore in range(98):
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=5.5, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- ["Greater than 1% GET exceeded 5 second response time"],
- logger.failures())
-
-
- def test_methodsCountedSeparately(self):
- """
- The counts for one method do not affect the results of another method.
- """
- logger = ReportStatistics()
- for _ignore in range(99):
- logger.observe(dict(
- type='response', method='GET', success=True,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='POST', success=True,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
-
- logger.observe(dict(
- type='response', method='GET', success=False,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='POST', success=False,
- duration=2.5, user='user01', client_type="test", client_id="1234"
- ))
-
- self.assertEqual([], logger.failures())
-
-
- def test_bucketRequest(self):
- """
- PUT(xxx-huge/large/medium/small} have different thresholds. Test that requests straddling
- each of those are correctly determined to be failures or not.
- """
-
- _thresholds = {
- "requests": {
- "limits": [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
- "thresholds": {
- "default": [100.0, 100.0, 100.0, 5.0, 1.0, 0.5, 0.0],
- "PUT{organizer-small}": [100.0, 50.0, 25.0, 5.0, 1.0, 0.5, 0.0],
- "PUT{organizer-medium}": [100.0, 100.0, 50.0, 25.0, 5.0, 1.0, 0.5],
- "PUT{organizer-large}": [100.0, 100.0, 100.0, 50.0, 25.0, 5.0, 1.0],
- "PUT{organizer-huge}": [100.0, 100.0, 100.0, 100.0, 100.0, 50.0, 25.0],
- }
- }
- }
-
- # -small below threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual([], logger.failures())
-
- # -small above 0.5 threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-small}', success=True,
- duration=0.6, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- ["Greater than 50% PUT{organizer-small} exceeded 0.5 second response time"],
- logger.failures()
- )
-
- # -medium below 0.5 threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=0.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=0.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=0.6, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- [],
- logger.failures()
- )
-
- # -medium above 1.0 threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=1.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=1.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-medium}', success=True,
- duration=1.6, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- ["Greater than 50% PUT{organizer-medium} exceeded 1 second response time"],
- logger.failures()
- )
-
- # -large below 1.0 threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=1.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=1.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=1.6, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- [],
- logger.failures()
- )
-
- # -large above 3.0 threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=0.2, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=3.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=3.6, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-large}', success=True,
- duration=3.6, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- ["Greater than 50% PUT{organizer-large} exceeded 3 second response time"],
- logger.failures()
- )
-
- # -huge below 10.0 threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=12.0, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=8, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=11.0, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=9.0, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- [],
- logger.failures()
- )
-
- # -huge above 10.0 threshold
- logger = ReportStatistics(thresholds=_thresholds)
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=12.0, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=9.0, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=12.0, user='user01', client_type="test", client_id="1234"
- ))
- logger.observe(dict(
- type='response', method='PUT{organizer-huge}', success=True,
- duration=42.42, user='user01', client_type="test", client_id="1234"
- ))
- self.assertEqual(
- ["Greater than 50% PUT{organizer-huge} exceeded 10 second response time"],
- logger.failures()
- )
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_profiles.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_profiles.py 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_profiles.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,1091 +0,0 @@
-##
-# Copyright (c) 2011-2015 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
-from contrib.performance.loadtest.profiles import RealisticInviter
-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 at example.com;PARTSTAT=AC
- CEPTED:urn:uuid:user01
-ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02 at example.com;PARTSTAT=NE
- EDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:urn:uuid:user02
-CREATED:20110124T170357Z
-DTSTAMP:20110124T170425Z
-ORGANIZER;CN=User 01;EMAIL=user01 at 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 at example.com;PARTSTAT=AC
- CEPTED:urn:uuid:user01
-ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02 at example.com;PARTSTAT=AC
- CEPTED:urn:uuid:user02
-CREATED:20110124T170357Z
-DTSTAMP:20110124T170425Z
-ORGANIZER;CN=User 01;EMAIL=user01 at 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 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at 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 at 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 at example.org" % (number,),
- "user%02d" % (number,))
- self.email = "mailto:user%02d at 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):
- self._events[href] = Event(self.serializePath, href, None, vevent)
- return succeed(None)
-
-
- def addInvite(self, href, vevent):
- return self.addEvent(href, vevent)
-
-
- 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))
- )
-
- 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)
-
-
-
-class InviterTests(TestCase):
- """
- Tests for loadtest.profiles.Inviter.
- """
- def setUp(self):
- self.sim = CalendarClientSimulator(
- AnyUser(), Populator(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 RealisticInviterTests(TestCase):
- """
- Tests for loadtest.profiles.RealisticInviter.
- """
- def setUp(self):
- self.sim = CalendarClientSimulator(
- AnyUser(), Populator(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 = RealisticInviter(None, self.sim, client, userNumber, **{"enabled": False})
- self.assertEqual(inviter.enabled, False)
-
- inviter = RealisticInviter(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 = RealisticInviter(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 = RealisticInviter(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 = RealisticInviter(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 at example.com" % (userNumber,), "mailto:user%02d at 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 = RealisticInviter(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 at example.com" % (selfNumber,), "mailto:user%02d at 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 = RealisticInviter(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 at example.com" % (selfNumber,),
- "mailto:user%02d at example.com" % (inviteeNumber,),
- "mailto:user%02d at 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 = RealisticInviter(Clock(), self.sim, client, userNumber)
- inviter.setParameters(
- inviteeDistribution=Deterministic(1),
- inviteeCountDistribution=Deterministic(2)
- )
- inviter._invite()
- self.assertEquals(len(client._events), 0)
-
-
-
-class AccepterTests(TestCase):
- """
- Tests for loadtest.profiles.Accepter.
- """
- def setUp(self):
- self.sim = CalendarClientSimulator(
- AnyUser(), Populator(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))
- )
- 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)
-
-
- 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 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())
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_sim.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_sim.py 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_sim.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,592 +0,0 @@
-##
-# Copyright (c) 2011-2015 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.
-#
-##
-
-from plistlib import writePlistToString
-from cStringIO import StringIO
-
-from twisted.python.log import msg
-from twisted.python.usage import UsageError
-from twisted.python.filepath import FilePath
-from twisted.internet.defer import Deferred, succeed
-from twisted.trial.unittest import TestCase
-
-from contrib.performance.stats import NormalDistribution
-from contrib.performance.loadtest.ical import OS_X_10_6
-from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
-from contrib.performance.loadtest.population import (
- SmoothRampUp, ClientType, PopulationParameters, Populator, CalendarClientSimulator,
- ProfileType, SimpleStatistics
-)
-from contrib.performance.loadtest.sim import (
- Arrival, SimOptions, LoadSimulator, LagTrackingReactor,
- _DirectoryRecord
-)
-
-
-VALID_CONFIG = {
- 'server': 'tcp:127.0.0.1:8008',
- 'webadmin': {
- 'enabled': True,
- 'HTTPPort': 8080,
- },
- 'arrival': {
- 'factory': 'contrib.performance.loadtest.population.SmoothRampUp',
- 'params': {
- 'groups': 10,
- 'groupSize': 1,
- 'interval': 3,
- },
- },
-}
-
-VALID_CONFIG_PLIST = writePlistToString(VALID_CONFIG)
-
-
-
-class SimOptionsTests(TestCase):
- def test_defaultConfig(self):
- """
- If the I{config} option is not specified, the default config.plist in
- the source tree is used.
- """
- options = SimOptions()
- self.assertEqual(options['config'], FilePath(__file__).sibling('config.plist'))
-
-
- def test_configFileNotFound(self):
- """
- If the filename given to the I{config} option is not found,
- L{SimOptions.parseOptions} raises a L{UsageError} indicating
- this.
- """
- name = FilePath(self.mktemp())
- options = SimOptions()
- exc = self.assertRaises(
- UsageError, options.parseOptions, ['--config', name.path])
- self.assertEquals(
- str(exc), "--config %s: No such file or directory" % (name.path,))
-
-
- def test_configFileNotParseable(self):
- """
- If the contents of the file given to the I{config} option
- cannot be parsed by L{ConfigParser},
- L{SimOptions.parseOptions} raises a L{UsageError} indicating
- this.
- """
- config = FilePath(self.mktemp())
- config.setContent("some random junk")
- options = SimOptions()
- exc = self.assertRaises(
- UsageError, options.parseOptions, ['--config', config.path])
- self.assertEquals(
- str(exc),
- "--config %s: syntax error: line 1, column 0" % (config.path,))
-
-
-
-class CalendarClientSimulatorTests(TestCase):
- """
- Tests for L{CalendarClientSimulator} which adds running clients to
- a simulation.
- """
- realmName = 'stub'
-
- def _user(self, name):
- password = 'password-' + name
- email = name + "@example.com"
- record = _DirectoryRecord(name, password, name, email, name)
- return record
-
-
- def test_createUser(self):
- """
- Subsequent calls to L{CalendarClientSimulator._createUser}
- with different user numbers return user details from different
- directory records.
- """
- calsim = CalendarClientSimulator(
- [self._user('alice'), self._user('bob'), self._user('carol')],
- Populator(None), None, None, 'http://example.org:1234/', None, None)
- users = sorted([
- calsim._createUser(0)[0],
- calsim._createUser(1)[0],
- calsim._createUser(2)[0],
- ])
- self.assertEqual(['alice', 'bob', 'carol'], users)
-
-
- def test_createUserAuthInfo(self):
- """
- The auth handler returned by L{CalendarClientSimulator._createUser}
- includes the password taken from user's directory record.
- """
- calsim = CalendarClientSimulator(
- [self._user('alice')],
- Populator(None), None, None, 'http://example.org:1234/', None, None)
- user, auth = calsim._createUser(0)
- self.assertEqual(
- auth['basic'].passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
- 'password-' + user)
- self.assertEqual(
- auth['digest'].passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
- 'password-' + user)
-
-
- def test_stop(self):
- """
- After L{CalendarClientSimulator.stop} is called, failed clients and
- profiles are not logged.
- """
- class BrokenClient(object):
- def __init__(self, reactor, serverAddress, principalPathTemplate, serializationPath, userInfo, auth, runResult):
- self._runResult = runResult
-
- def run(self):
- return self._runResult
-
- def stop(self):
- return succeed(None)
-
- class BrokenProfile(object):
- def __init__(self, reactor, simulator, client, userNumber, runResult):
- self._runResult = runResult
- self.enabled = True
-
- def initialize(self):
- return succeed(None)
-
- def run(self):
- return self._runResult
-
- clientRunResult = Deferred()
- profileRunResult = Deferred()
-
- params = PopulationParameters()
- params.addClient(1, ClientType(
- BrokenClient, {'runResult': clientRunResult},
- [ProfileType(BrokenProfile, {'runResult': profileRunResult})])
- )
- sim = CalendarClientSimulator(
- [self._user('alice')], Populator(None), params, None, 'http://example.com:1234/', None, None)
- sim.add(1, 1)
- sim.stop()
- clientRunResult.errback(RuntimeError("Some fictional client problem"))
- profileRunResult.errback(RuntimeError("Some fictional profile problem"))
-
- self.assertEqual([], self.flushLoggedErrors())
-
-
-
-class Reactor(object):
- message = "some event to be observed"
-
- def __init__(self):
- self._triggers = []
- self._whenRunning = []
-
-
- def run(self):
- for thunk in self._whenRunning:
- thunk()
- msg(thingo=self.message)
- for _ignore_phase, event, thunk in self._triggers:
- if event == 'shutdown':
- thunk()
-
-
- def callWhenRunning(self, thunk):
- self._whenRunning.append(thunk)
-
-
- def addSystemEventTrigger(self, phase, event, thunk):
- self._triggers.append((phase, event, thunk))
-
-
-
-class Observer(object):
- def __init__(self):
- self.reported = False
- self.events = []
-
-
- def observe(self, event):
- self.events.append(event)
-
-
- def report(self, output):
- self.reported = True
-
-
- def failures(self):
- return []
-
-
-
-class NullArrival(object):
- def run(self, sim):
- pass
-
-
-
-class StubSimulator(LoadSimulator):
- def run(self):
- return 3
-
-
-
-class LoadSimulatorTests(TestCase):
- def test_main(self):
- """
- L{LoadSimulator.main} raises L{SystemExit} with the result of
- L{LoadSimulator.run}.
- """
- config = FilePath(self.mktemp())
- config.setContent(VALID_CONFIG_PLIST)
-
- exc = self.assertRaises(
- SystemExit, StubSimulator.main, ['--config', config.path])
- self.assertEquals(
- exc.args, (StubSimulator(None, None, None, None, None, None, None).run(),))
-
-
- def test_createSimulator(self):
- """
- L{LoadSimulator.createSimulator} creates a L{CalendarClientSimulator}
- with its own reactor and host and port information from the
- configuration file.
- """
- server = 'http://127.0.0.7:1243/'
- reactor = object()
- sim = LoadSimulator(server, None, None, None, None, None, None, reactor=reactor)
- calsim = sim.createSimulator()
- self.assertIsInstance(calsim, CalendarClientSimulator)
- self.assertIsInstance(calsim.reactor, LagTrackingReactor)
- self.assertIdentical(calsim.reactor._reactor, reactor)
- self.assertEquals(calsim.server, server)
-
-
- def test_loadAccountsFromFile(self):
- """
- L{LoadSimulator.fromCommandLine} takes an account loader from the
- config file and uses it to create user records for use in the
- simulation.
- """
- accounts = FilePath(self.mktemp())
- accounts.setContent("foo,bar,baz,quux,goo\nfoo2,bar2,baz2,quux2,goo2\n")
- config = VALID_CONFIG.copy()
- config["accounts"] = {
- "loader": "contrib.performance.loadtest.sim.recordsFromCSVFile",
- "params": {
- "path": accounts.path
- },
- }
- configpath = FilePath(self.mktemp())
- configpath.setContent(writePlistToString(config))
- io = StringIO()
- sim = LoadSimulator.fromCommandLine(['--config', configpath.path], io)
- self.assertEquals(io.getvalue(), "Loaded 2 accounts.\n")
- self.assertEqual(2, len(sim.records))
- self.assertEqual(sim.records[0].uid, 'foo')
- self.assertEqual(sim.records[0].password, 'bar')
- self.assertEqual(sim.records[0].commonName, 'baz')
- self.assertEqual(sim.records[0].email, 'quux')
- self.assertEqual(sim.records[1].uid, 'foo2')
- self.assertEqual(sim.records[1].password, 'bar2')
- self.assertEqual(sim.records[1].commonName, 'baz2')
- self.assertEqual(sim.records[1].email, 'quux2')
-
-
- def test_loadDefaultAccountsFromFile(self):
- """
- L{LoadSimulator.fromCommandLine} takes an account loader (with
- empty path)from the config file and uses it to create user
- records for use in the simulation.
- """
- config = VALID_CONFIG.copy()
- config["accounts"] = {
- "loader": "contrib.performance.loadtest.sim.recordsFromCSVFile",
- "params": {
- "path": ""
- },
- }
- configpath = FilePath(self.mktemp())
- configpath.setContent(writePlistToString(config))
- sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
- StringIO())
- self.assertEqual(99, len(sim.records))
- self.assertEqual(sim.records[0].uid, 'user01')
- self.assertEqual(sim.records[0].password, 'user01')
- self.assertEqual(sim.records[0].commonName, 'User 01')
- self.assertEqual(sim.records[0].email, 'user01 at example.com')
- self.assertEqual(sim.records[98].uid, 'user99')
- self.assertEqual(sim.records[98].password, 'user99')
- self.assertEqual(sim.records[98].commonName, 'User 99')
- self.assertEqual(sim.records[98].email, 'user99 at example.com')
-
-
- def test_generateRecordsDefaultPatterns(self):
- """
- L{LoadSimulator.fromCommandLine} takes an account loader from the
- config file and uses it to generate user records for use in the
- simulation.
- """
- config = VALID_CONFIG.copy()
- config["accounts"] = {
- "loader": "contrib.performance.loadtest.sim.generateRecords",
- "params": {
- "count": 2
- },
- }
- configpath = FilePath(self.mktemp())
- configpath.setContent(writePlistToString(config))
- sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
- StringIO())
- self.assertEqual(2, len(sim.records))
- self.assertEqual(sim.records[0].uid, 'user1')
- self.assertEqual(sim.records[0].password, 'user1')
- self.assertEqual(sim.records[0].commonName, 'User 1')
- self.assertEqual(sim.records[0].email, 'user1 at example.com')
- self.assertEqual(sim.records[1].uid, 'user2')
- self.assertEqual(sim.records[1].password, 'user2')
- self.assertEqual(sim.records[1].commonName, 'User 2')
- self.assertEqual(sim.records[1].email, 'user2 at example.com')
-
-
- def test_generateRecordsNonDefaultPatterns(self):
- """
- L{LoadSimulator.fromCommandLine} takes an account loader from the
- config file and uses it to generate user records for use in the
- simulation.
- """
- config = VALID_CONFIG.copy()
- config["accounts"] = {
- "loader": "contrib.performance.loadtest.sim.generateRecords",
- "params": {
- "count": 3,
- "uidPattern": "USER%03d",
- "passwordPattern": "PASSWORD%03d",
- "namePattern": "Test User %03d",
- "emailPattern": "USER%03d at example2.com",
- },
- }
- configpath = FilePath(self.mktemp())
- configpath.setContent(writePlistToString(config))
- sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
- StringIO())
- self.assertEqual(3, len(sim.records))
- self.assertEqual(sim.records[0].uid, 'USER001')
- self.assertEqual(sim.records[0].password, 'PASSWORD001')
- self.assertEqual(sim.records[0].commonName, 'Test User 001')
- self.assertEqual(sim.records[0].email, 'USER001 at example2.com')
- self.assertEqual(sim.records[2].uid, 'USER003')
- self.assertEqual(sim.records[2].password, 'PASSWORD003')
- self.assertEqual(sim.records[2].commonName, 'Test User 003')
- self.assertEqual(sim.records[2].email, 'USER003 at example2.com')
-
-
- def test_specifyRuntime(self):
- """
- L{LoadSimulator.fromCommandLine} recognizes the I{--runtime} option to
- specify a limit on how long the simulation will run.
- """
- config = FilePath(self.mktemp())
- config.setContent(VALID_CONFIG_PLIST)
- sim = LoadSimulator.fromCommandLine(['--config', config.path, '--runtime', '123'])
- self.assertEqual(123, sim.runtime)
-
-
- def test_loadServerConfig(self):
- """
- The Calendar Server host and port are loaded from the [server]
- section of the configuration file specified.
- """
- config = FilePath(self.mktemp())
- config.setContent(
- writePlistToString({"server": "https://127.0.0.3:8432/"})
- )
- sim = LoadSimulator.fromCommandLine(['--config', config.path])
- self.assertEquals(sim.server, "https://127.0.0.3:8432/")
-
-
- def test_loadArrivalConfig(self):
- """
- The arrival policy type and arguments are loaded from the
- [arrival] section of the configuration file specified.
- """
- config = FilePath(self.mktemp())
- config.setContent(
- writePlistToString({
- "arrival": {
- "factory": "contrib.performance.loadtest.population.SmoothRampUp",
- "params": {
- "groups": 10,
- "groupSize": 1,
- "interval": 3,
- },
- },
- })
- )
- sim = LoadSimulator.fromCommandLine(['--config', config.path])
- self.assertEquals(
- sim.arrival,
- Arrival(SmoothRampUp, dict(groups=10, groupSize=1, interval=3)))
-
-
- def test_createArrivalPolicy(self):
- """
- L{LoadSimulator.createArrivalPolicy} creates an arrival
- policy based on the L{Arrival} passed to its initializer.
- """
- class FakeArrival(object):
- def __init__(self, reactor, x, y):
- self.reactor = reactor
- self.x = x
- self.y = y
-
- reactor = object()
- sim = LoadSimulator(
- None, None, None, None, None, Arrival(FakeArrival, {'x': 3, 'y': 2}), None, reactor=reactor)
- arrival = sim.createArrivalPolicy()
- self.assertIsInstance(arrival, FakeArrival)
- self.assertIdentical(arrival.reactor, sim.reactor)
- self.assertEquals(arrival.x, 3)
- self.assertEquals(arrival.y, 2)
-
-
- def test_loadPopulationParameters(self):
- """
- Client weights and profiles are loaded from the [clients]
- section of the configuration file specified.
- """
- config = FilePath(self.mktemp())
- config.setContent(
- writePlistToString(
- {
- "clients": [
- {
- "software": "contrib.performance.loadtest.ical.OS_X_10_6",
- "params": {
- "foo": "bar"
- },
- "profiles": [
- {
- "params": {
- "interval": 25,
- "eventStartDistribution": {
- "type": "contrib.performance.stats.NormalDistribution",
- "params": {
- "mu": 123,
- "sigma": 456,
- }
- }
- },
- "class": "contrib.performance.loadtest.profiles.Eventer"
- }
- ],
- "weight": 3,
- }
- ]
- }
- )
- )
-
- sim = LoadSimulator.fromCommandLine(
- ['--config', config.path, '--clients', config.path]
- )
- expectedParameters = PopulationParameters()
- expectedParameters.addClient(
- 3,
- ClientType(
- OS_X_10_6,
- {"foo": "bar"},
- [
- ProfileType(
- Eventer, {
- "interval": 25,
- "eventStartDistribution": NormalDistribution(123, 456)
- }
- )
- ]
- )
- )
- self.assertEquals(sim.parameters, expectedParameters)
-
-
- def test_requireClient(self):
- """
- At least one client is required, so if a configuration with an
- empty clients array is specified, a single default client type
- is used.
- """
- config = FilePath(self.mktemp())
- config.setContent(writePlistToString({"clients": []}))
- sim = LoadSimulator.fromCommandLine(
- ['--config', config.path, '--clients', config.path]
- )
- expectedParameters = PopulationParameters()
- expectedParameters.addClient(
- 1, ClientType(OS_X_10_6, {}, [Eventer, Inviter, Accepter]))
- self.assertEquals(sim.parameters, expectedParameters)
-
-
- def test_loadLogObservers(self):
- """
- Log observers specified in the [observers] section of the
- configuration file are added to the logging system.
- """
- config = FilePath(self.mktemp())
- config.setContent(
- writePlistToString(
- {
- "observers": [
- {
- "type": "contrib.performance.loadtest.population.SimpleStatistics",
- "params": {},
- },
- ]
- }
- )
- )
- sim = LoadSimulator.fromCommandLine(['--config', config.path])
- self.assertEquals(len(sim.observers), 1)
- self.assertIsInstance(sim.observers[0], SimpleStatistics)
-
-
- def test_observeRunReport(self):
- """
- Each log observer is added to the log publisher before the
- simulation run is started and has its C{report} method called
- after the simulation run completes.
- """
- observers = [Observer()]
- sim = LoadSimulator(
- "http://example.com:123/",
- "/principals/users/%s/",
- None,
- None,
- None,
- Arrival(lambda reactor: NullArrival(), {}),
- None, observers, reactor=Reactor())
- io = StringIO()
- sim.run(io)
- self.assertEquals(io.getvalue(), "\n*** PASS\n")
- self.assertTrue(observers[0].reported)
- self.assertEquals(
- [e for e in observers[0].events if "thingo" in e][0]["thingo"],
- Reactor.message
- )
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_trafficlogger.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_trafficlogger.py 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_trafficlogger.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,210 +0,0 @@
-##
-# Copyright (c) 2011-2015 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.
-#
-##
-
-from zope.interface import Interface, implements
-
-from twisted.internet.protocol import ClientFactory, Protocol
-from twisted.trial.unittest import TestCase
-from twisted.test.proto_helpers import StringTransport, MemoryReactor
-from twisted.protocols.wire import Discard
-
-from contrib.performance.loadtest.trafficlogger import _TrafficLoggingFactory, loggedReactor
-
-
-class IProbe(Interface):
- """
- An interface which can be used to verify some interface-related behavior of
- L{loggedReactor}.
- """
- def probe(): #@NoSelf
- pass
-
-
-
-class Probe(object):
- implements(IProbe)
-
- _probed = False
-
- def __init__(self, result=None):
- self._result = result
-
-
- def probe(self):
- self._probed = True
- return self._result
-
-
-
-class TrafficLoggingReactorTests(TestCase):
- """
- Tests for L{loggedReactor}.
- """
- def test_nothing(self):
- """
- L{loggedReactor} returns the object passed to it, if the object passed
- to it doesn't provide any interfaces. This is mostly for testing
- convenience rather than a particularly useful feature.
- """
- probe = object()
- self.assertIdentical(probe, loggedReactor(probe))
-
-
- def test_interfaces(self):
- """
- The object returned by L{loggedReactor} provides all of the interfaces
- provided by the object passed to it.
- """
- probe = Probe()
- reactor = loggedReactor(probe)
- self.assertTrue(IProbe.providedBy(reactor))
-
-
- def test_passthrough(self):
- """
- Methods on interfaces on the object passed to L{loggedReactor} can be
- called by calling them on the object returned by L{loggedReactor}.
- """
- expected = object()
- probe = Probe(expected)
- reactor = loggedReactor(probe)
- result = reactor.probe()
- self.assertTrue(probe._probed)
- self.assertIdentical(expected, result)
-
-
- def test_connectTCP(self):
- """
- Called on the object returned by L{loggedReactor}, C{connectTCP} calls
- the wrapped reactor's C{connectTCP} method with the original factory
- wrapped in a L{_TrafficLoggingFactory}.
- """
- class RecordDataProtocol(Protocol):
- def dataReceived(self, data):
- self.data = data
- proto = RecordDataProtocol()
- factory = ClientFactory()
- factory.protocol = lambda: proto
- reactor = MemoryReactor()
- logged = loggedReactor(reactor)
- logged.connectTCP('192.168.1.2', 1234, factory, 21, '127.0.0.2')
- [(host, port, factory, timeout, bindAddress)] = reactor.tcpClients
- self.assertEqual('192.168.1.2', host)
- self.assertEqual(1234, port)
- self.assertIsInstance(factory, _TrafficLoggingFactory)
- self.assertEqual(21, timeout)
- self.assertEqual('127.0.0.2', bindAddress)
-
- # Verify that the factory and protocol specified are really being used
- protocol = factory.buildProtocol(None)
- protocol.makeConnection(None)
- protocol.dataReceived("foo")
- self.assertEqual(proto.data, "foo")
-
-
- def test_getLogFiles(self):
- """
- The reactor returned by L{loggedReactor} has a C{getLogFiles} method
- which returns a L{logstate} instance containing the active and
- completed log files tracked by the logging wrapper.
- """
- wrapped = ClientFactory()
- wrapped.protocol = Discard
- reactor = MemoryReactor()
- logged = loggedReactor(reactor)
- logged.connectTCP('127.0.0.1', 1234, wrapped)
- factory = reactor.tcpClients[0][2]
-
- finished = factory.buildProtocol(None)
- finished.makeConnection(StringTransport())
- finished.dataReceived('finished')
- finished.connectionLost(None)
-
- active = factory.buildProtocol(None)
- active.makeConnection(StringTransport())
- active.dataReceived('active')
-
- logs = logged.getLogFiles()
- self.assertEqual(1, len(logs.finished))
- self.assertIn('finished', logs.finished[0].getvalue())
- self.assertEqual(1, len(logs.active))
- self.assertIn('active', logs.active[0].getvalue())
-
-
-
-class TrafficLoggingFactoryTests(TestCase):
- """
- Tests for L{_TrafficLoggingFactory}.
- """
- def setUp(self):
- self.wrapped = ClientFactory()
- self.wrapped.protocol = Discard
- self.factory = _TrafficLoggingFactory(self.wrapped)
-
-
- def test_receivedBytesLogged(self):
- """
- When bytes are delivered through a protocol created by
- L{_TrafficLoggingFactory}, they are added to a log kept on that
- factory.
- """
- protocol = self.factory.buildProtocol(None)
-
- # The factory should now have a new StringIO log file
- self.assertEqual(1, len(self.factory.logs))
-
- transport = StringTransport()
- protocol.makeConnection(transport)
-
- protocol.dataReceived("hello, world")
- self.assertEqual(
- "*\nC 0: 'hello, world'\n", self.factory.logs[0].getvalue())
-
-
- def test_finishedLogs(self):
- """
- When connections are lost, the corresponding log files are moved into
- C{_TrafficLoggingFactory.finishedLogs}.
- """
- protocol = self.factory.buildProtocol(None)
- transport = StringTransport()
- protocol.makeConnection(transport)
- logfile = self.factory.logs[0]
- protocol.connectionLost(None)
- self.assertEqual(0, len(self.factory.logs))
- self.assertEqual([logfile], self.factory.finishedLogs)
-
-
- def test_finishedLogsLimit(self):
- """
- Only the most recent C{_TrafficLoggingFactory.LOGFILE_LIMIT} logfiles
- are kept in C{_TrafficLoggingFactory.finishedLogs}.
- """
- self.factory.LOGFILE_LIMIT = 2
- first = self.factory.buildProtocol(None)
- first.makeConnection(StringTransport())
- second = self.factory.buildProtocol(None)
- second.makeConnection(StringTransport())
- third = self.factory.buildProtocol(None)
- third.makeConnection(StringTransport())
-
- second.connectionLost(None)
- first.connectionLost(None)
- third.connectionLost(None)
-
- self.assertEqual(
- [first.logfile, third.logfile], self.factory.finishedLogs)
Deleted: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_webadmin.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_webadmin.py 2015-08-17 20:38:08 UTC (rev 15047)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/test_webadmin.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -1,144 +0,0 @@
-##
-# Copyright (c) 2012-2015 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.
-#
-##
-
-from twisted.trial.unittest import TestCase
-from contrib.performance.loadtest.webadmin import LoadSimAdminResource
-
-class WebAdminTests(TestCase):
- """
- Tests for L{LoadSimAdminResource}.
- """
-
- class FakeReporter(object):
-
- def generateReport(self, output):
- output.write("FakeReporter")
-
-
- class FakeReactor(object):
-
- def __init__(self):
- self.running = True
-
- def stop(self):
- self.running = False
-
-
- class FakeLoadSim(object):
-
- def __init__(self):
- self.reactor = WebAdminTests.FakeReactor()
- self.reporter = WebAdminTests.FakeReporter()
- self.running = True
-
- def stop(self):
- self.running = False
-
-
- class FakeRequest(object):
-
- def __init__(self, **kwargs):
- self.args = kwargs
-
-
- def test_resourceGET(self):
- """
- Test render_GET
- """
-
- loadsim = WebAdminTests.FakeLoadSim()
- resource = LoadSimAdminResource(loadsim)
-
- response = resource.render_GET(WebAdminTests.FakeRequest())
- self.assertTrue(response.startswith("<html>"))
- self.assertTrue(response.find(resource.token) != -1)
-
-
- def test_resourcePOST_Stop(self):
- """
- Test render_POST when Stop button is clicked
- """
-
- loadsim = WebAdminTests.FakeLoadSim()
- resource = LoadSimAdminResource(loadsim)
- self.assertTrue(loadsim.reactor.running)
-
- response = resource.render_POST(WebAdminTests.FakeRequest(
- token=(resource.token,),
- stop=None,
- ))
- self.assertTrue(response.startswith("<html>"))
- self.assertTrue(response.find(resource.token) == -1)
- self.assertTrue(response.find("FakeReporter") != -1)
- self.assertFalse(loadsim.running)
-
-
- def test_resourcePOST_Stop_BadToken(self):
- """
- Test render_POST when Stop button is clicked but token is wrong
- """
-
- loadsim = WebAdminTests.FakeLoadSim()
- resource = LoadSimAdminResource(loadsim)
- self.assertTrue(loadsim.reactor.running)
-
- response = resource.render_POST(WebAdminTests.FakeRequest(
- token=("xyz",),
- stop=None,
- ))
- self.assertTrue(response.startswith("<html>"))
- self.assertTrue(response.find(resource.token) != -1)
- self.assertTrue(response.find("FakeReporter") == -1)
- self.assertTrue(loadsim.running)
-
-
- def test_resourcePOST_Results(self):
- """
- Test render_POST when Results button is clicked
- """
-
- loadsim = WebAdminTests.FakeLoadSim()
- resource = LoadSimAdminResource(loadsim)
- self.assertTrue(loadsim.reactor.running)
-
- response = resource.render_POST(WebAdminTests.FakeRequest(
- token=(resource.token,),
- results=None,
- ))
- self.assertTrue(response.startswith("<html>"))
- self.assertTrue(response.find(resource.token) != -1)
- self.assertTrue(response.find("FakeReporter") != -1)
- self.assertTrue(loadsim.running)
-
-
- def test_resourcePOST_Results_BadToken(self):
- """
- Test render_POST when Results button is clicked and token is wrong
- """
-
- loadsim = WebAdminTests.FakeLoadSim()
- resource = LoadSimAdminResource(loadsim)
- self.assertTrue(loadsim.reactor.running)
-
- response = resource.render_POST(WebAdminTests.FakeRequest(
- token=("xyz",),
- results=None,
- ))
- self.assertTrue(response.startswith("<html>"))
- self.assertTrue(response.find(resource.token) != -1)
- self.assertTrue(response.find("FakeReporter") == -1)
- self.assertTrue(loadsim.running)
Added: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py (rev 0)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py 2015-08-17 21:14:42 UTC (rev 15048)
@@ -0,0 +1,101 @@
+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.distributions import (
+ LogNormalDistribution, UniformDiscreteDistribution,
+ UniformIntegerDistribution, WorkDistribution, RecurrenceDistribution
+)
+
+from pycalendar.datetime import DateTime
+from pycalendar.timezone import Timezone
+
+class DistributionTests(TestCase):
+ def test_lognormal(self):
+ dist = LogNormalDistribution(mu=1, sigma=1)
+ for _ignore_i in range(100):
+ value = dist.sample()
+ self.assertIsInstance(value, float)
+ self.assertTrue(value >= 0.0, "negative value %r" % (value,))
+ self.assertTrue(value <= 1000, "implausibly high value %r" % (value,))
+
+ dist = LogNormalDistribution(mode=1, median=2)
+ for _ignore_i in range(100):
+ value = dist.sample()
+ self.assertIsInstance(value, float)
+ self.assertTrue(value >= 0.0, "negative value %r" % (value,))
+ self.assertTrue(value <= 1000, "implausibly high value %r" % (value,))
+
+ dist = LogNormalDistribution(mode=1, mean=2)
+ for _ignore_i in range(100):
+ value = dist.sample()
+ self.assertIsInstance(value, float)
+ self.assertTrue(value >= 0.0, "negative value %r" % (value,))
+ self.assertTrue(value <= 1000, "implausibly high value %r" % (value,))
+
+ self.assertRaises(ValueError, LogNormalDistribution, mu=1)
+ self.assertRaises(ValueError, LogNormalDistribution, sigma=1)
+ self.assertRaises(ValueError, LogNormalDistribution, mode=1)
+ self.assertRaises(ValueError, LogNormalDistribution, mean=1)
+ self.assertRaises(ValueError, LogNormalDistribution, median=1)
+
+
+ def test_uniformdiscrete(self):
+ population = [1, 5, 6, 9]
+ counts = dict.fromkeys(population, 0)
+ dist = UniformDiscreteDistribution(population)
+ for _ignore_i in range(len(population) * 10):
+ counts[dist.sample()] += 1
+ self.assertEqual(dict.fromkeys(population, 10), counts)
+
+
+ def test_workdistribution(self):
+ tzname = "US/Eastern"
+ dist = WorkDistribution(["mon", "wed", "thu", "sat"], 10, 20, tzname)
+ dist._helperDistribution = UniformDiscreteDistribution([35 * 60 * 60 + 30 * 60])
+ dist.now = lambda tzname = None: DateTime(2011, 5, 29, 18, 5, 36, tzid=tzname)
+ value = dist.sample()
+ self.assertEqual(
+ # Move past three workdays - monday, wednesday, thursday - using 30
+ # of the hours, and then five and a half hours into the fourth
+ # workday, saturday. Workday starts at 10am, so the sample value
+ # is 3:30pm, ie 1530 hours.
+ DateTime(2011, 6, 4, 15, 30, 0, tzid=Timezone(tzid=tzname)),
+ value
+ )
+
+ dist = WorkDistribution(["mon", "tue", "wed", "thu", "fri"], 10, 20, tzname)
+ dist._helperDistribution = UniformDiscreteDistribution([35 * 60 * 60 + 30 * 60])
+ value = dist.sample()
+ self.assertTrue(isinstance(value, DateTime))
+
+ # twisted.trial.unittest.FailTest: not equal:
+ # a = datetime.datetime(2011, 6, 4, 15, 30, tzinfo=<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>)
+ # b = datetime.datetime(2011, 6, 4, 19, 30, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
+ # test_workdistribution.todo = "Somehow timezones mess this up"
+
+
+ def test_recurrencedistribution(self):
+ dist = RecurrenceDistribution(False)
+ for _ignore in range(100):
+ value = dist.sample()
+ self.assertTrue(value is None)
+
+ dist = RecurrenceDistribution(True, {"daily": 1, "none": 2, "weekly": 1})
+ dist._helperDistribution = UniformDiscreteDistribution([0, 3, 2, 1, 0], randomize=False)
+ value = dist.sample()
+ self.assertTrue(value is not None)
+ value = dist.sample()
+ self.assertTrue(value is None)
+ value = dist.sample()
+ self.assertTrue(value is None)
+ value = dist.sample()
+ self.assertTrue(value is not None)
+ value = dist.sample()
+ self.assertTrue(value is not None)
+
+
+ def test_uniform(self):
+ dist = UniformIntegerDistribution(-5, 10)
+ for _ignore_i in range(100):
+ value = dist.sample()
+ self.assertTrue(-5 <= value < 10)
+ self.assertIsInstance(value, int)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150817/b0b5fe2b/attachment-0001.html>
More information about the calendarserver-changes
mailing list