3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * File for the CiviCRM APIv3 job functions
31 * @package CiviCRM_APIv3
34 * @copyright CiviCRM LLC (c) 2004-2016
35 * @version $Id: Job.php 30879 2010-11-22 15:45:55Z shot $
40 * Class api_v3_JobTest
43 class api_v3_JobProcessMailingTest
extends CiviUnitTestCase
{
44 protected $_apiversion = 3;
46 public $DBResetRequired = FALSE;
47 public $_entity = 'Job';
48 public $_params = array();
52 protected $defaultSettings;
59 public function setUp() {
60 $this->cleanupMailingTest();
62 CRM_Mailing_BAO_MailingJob
::$mailsProcessed = 0; // DGW
63 $this->_groupID
= $this->groupCreate();
64 $this->_email
= 'test@test.test';
65 $this->_params
= array(
66 'subject' => 'Accidents in cars cause children',
67 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.',
68 'name' => 'mailing name',
70 'groups' => array('include' => array($this->_groupID
)),
71 'scheduled_date' => 'now',
73 $this->defaultSettings
= array(
74 'mailings' => 1, // int, #mailings to send
75 'recipients' => 20, // int, #contacts to receive mailing
76 'workers' => 1, // int, #concurrent cron jobs
77 'iterations' => 1, // int, #times to spawn all the workers
78 'lockHold' => 0, // int, #extra seconds each cron job should hold lock
79 'mailerBatchLimit' => 0, // int, max# recipients to send in a given cron run
80 'mailerJobsMax' => 0, // int, max# concurrent jobs
81 'mailerJobSize' => 0, // int, max# recipients in each job
82 'mailThrottleTime' => 0, // int, microseconds separating messages
84 $this->_mut
= new CiviMailUtils($this, TRUE);
85 $this->callAPISuccess('mail_settings', 'get', array('api.mail_settings.create' => array('domain' => 'chaos.org')));
90 public function tearDown() {
91 //$this->_mut->clearMessages();
93 CRM_Utils_Hook
::singleton()->reset();
94 CRM_Mailing_BAO_MailingJob
::$mailsProcessed = 0; // DGW
95 //$this->cleanupMailingTest();
99 public function testBasic() {
100 $this->createContactsInGroup(10, $this->_groupID
);
101 Civi
::settings()->add(array(
102 'mailerBatchLimit' => 2,
104 $this->callAPISuccess('mailing', 'create', $this->_params
);
105 $this->_mut
->assertRecipients(array());
106 $this->callAPISuccess('job', 'process_mailing', array());
107 $this->_mut
->assertRecipients($this->getRecipients(1, 2));
110 public function concurrencyExamples() {
113 // Launch 3 workers, but mailerJobsMax limits us to 1 worker.
118 // FIXME: lockHold is unrealistic/unrepresentative. In reality, this situation fails because
119 // the data.* locks trample the worker.* locks. However, setting lockHold allows us to
120 // approximate the behavior of what would happen *if* the lock-implementation didn't suffer
121 // trampling effects.
123 'mailerBatchLimit' => 4,
124 'mailerJobsMax' => 1,
127 0 => 2, // 2 jobs which produce 0 messages
128 4 => 1, // 1 job which produces 4 messages
133 // Launch 3 workers, but mailerJobsMax limits us to 2 workers.
138 // FIXME: lockHold is unrealistic/unrepresentative. In reality, this situation fails because
139 // the data.* locks trample the worker.* locks. However, setting lockHold allows us to
140 // approximate the behavior of what would happen *if* the lock-implementation didn't suffer
141 // trampling effects.
143 'mailerBatchLimit' => 5,
144 'mailerJobsMax' => 2,
147 0 => 1, // 1 job which produce 0 messages
148 5 => 2, // 2 jobs which produce 5 messages
153 // Launch 3 workers and saturate them (mailerJobsMax=3)
158 'mailerBatchLimit' => 6,
159 'mailerJobsMax' => 3,
162 6 => 3, // 3 jobs which produce 6 messages
167 // Launch 4 workers and saturate them (mailerJobsMax=0)
172 'mailerBatchLimit' => 6,
173 'mailerJobsMax' => 0,
176 6 => 3, // 3 jobs which produce 6 messages
177 2 => 1, // 1 job which produces 2 messages
182 // Launch 1 worker, 3 times in a row. Deliver everything.
188 'mailerBatchLimit' => 7,
191 7 => 1, // 1 job which produces 7 messages
192 3 => 1, // 1 job which produces 3 messages
193 0 => 1, // 1 job which produces 0 messages
198 // Launch 2 worker, 3 times in a row. Deliver everything.
204 'mailerBatchLimit' => 3,
207 3 => 3, // 3 jobs which produce 3 messages
208 1 => 1, // 1 job which produces 1 messages
209 0 => 2, // 2 jobs which produce 0 messages
214 // For two mailings, launch 1 worker, 5 times in a row. Deliver everything.
221 'mailerBatchLimit' => 6,
224 // x6 => x4+x2 => x6 => x2 => x0
225 6 => 3, // 3 jobs which produce 6 messages
226 2 => 1, // 1 job which produces 2 messages
227 0 => 1, // 1 job which produces 0 messages
236 * Setup various mail configuration options (eg $mailerBatchLimit,
237 * $mailerJobMax) and spawn multiple worker threads ($workers).
238 * Allow the threads to complete. (Optionally, repeat the above
239 * process.) Finally, check to see if the right number of
240 * jobs delivered the right number of messages.
242 * @param array $settings
243 * An array of settings (eg mailerBatchLimit, workers). See comments
244 * for $this->defaultSettings.
245 * @param array $expectedTallies
246 * A listing of the number cron-runs keyed by their size.
247 * For example, array(10=>2) means that there 2 cron-runs
248 * which delivered 10 messages each.
249 * @param int $expectedTotal
250 * The total number of contacts for whom messages should have
252 * @dataProvider concurrencyExamples
254 public function testConcurrency($settings, $expectedTallies, $expectedTotal) {
255 $settings = array_merge($this->defaultSettings
, $settings);
257 $this->createContactsInGroup($settings['recipients'], $this->_groupID
);
258 Civi
::settings()->add(CRM_Utils_Array
::subset($settings, array(
264 for ($i = 0; $i < $settings['mailings']; $i++
) {
265 $this->callAPISuccess('mailing', 'create', $this->_params
);
268 $this->_mut
->assertRecipients(array());
270 $allApiResults = array();
271 for ($iterationId = 0; $iterationId < $settings['iterations']; $iterationId++
) {
272 $apiCalls = $this->createExternalAPI();
273 $apiCalls->addEnv(array('CIVICRM_CRON_HOLD' => $settings['lockHold']));
274 for ($workerId = 0; $workerId < $settings['workers']; $workerId++
) {
275 $apiCalls->addCall('job', 'process_mailing', array());
278 $this->assertEquals($settings['workers'], $apiCalls->getRunningCount());
281 $allApiResults = array_merge($allApiResults, $apiCalls->getResults());
284 $actualTallies = $this->tallyApiResults($allApiResults);
285 $this->assertEquals($expectedTallies, $actualTallies, 'API tallies should match.' . print_r(array(
286 'expectedTallies' => $expectedTallies,
287 'actualTallies' => $actualTallies,
288 'apiResults' => $allApiResults,
290 $this->_mut
->assertRecipients($this->getRecipients(1, $expectedTotal / $settings['mailings'], 'nul.example.com', $settings['mailings']));
291 $this->assertEquals(0, $apiCalls->getRunningCount());
295 * Create contacts in group.
298 * @param int $groupID
299 * @param string $domain
301 public function createContactsInGroup($count, $groupID, $domain = 'nul.example.com') {
302 for ($i = 1; $i <= $count; $i++
) {
303 $contactID = $this->individualCreate(array('first_name' => $count, 'email' => 'mail' . $i . '@' . $domain));
304 $this->callAPISuccess('group_contact', 'create', array(
305 'contact_id' => $contactID,
306 'group_id' => $groupID,
313 * Construct the list of email addresses for $count recipients.
317 * @param string $domain
318 * @param int $mailings
322 public function getRecipients($start, $count, $domain = 'nul.example.com', $mailings = 1) {
323 $recipients = array();
324 for ($m = 0; $m < $mailings; $m++
) {
325 for ($i = $start; $i < ($start +
$count); $i++
) {
326 $recipients[][0] = 'mail' . $i . '@' . $domain;
332 protected function cleanupMailingTest() {
333 $this->quickCleanup(array(
335 'civicrm_mailing_job',
336 'civicrm_mailing_spool',
337 'civicrm_mailing_group',
338 'civicrm_mailing_recipients',
339 'civicrm_mailing_event_queue',
340 'civicrm_mailing_event_bounce',
341 'civicrm_mailing_event_delivered',
343 'civicrm_group_contact',
349 * Categorize results based on (a) whether they succeeded
350 * and (b) the number of messages sent.
352 * @param array $apiResults
354 * One key 'error' for all failures.
355 * A separate key for each distinct quantity.
357 protected function tallyApiResults($apiResults) {
359 foreach ($apiResults as $apiResult) {
360 $key = !empty($apiResult['is_error']) ?
'error' : $apiResult['values']['processed'];
361 $ret[$key] = !empty($ret[$key]) ?
1 +
$ret[$key] : 1;