Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
require_once(dirname(__FILE__).'/../vendor/lime/lime.php');
4
 
5
/*
6
 * This file is part of the symfony package.
7
 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
 
13
/**
14
 * sfTestFunctional tests an application by using a browser simulator.
15
 *
16
 * @package    symfony
17
 * @subpackage test
18
 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
19
 * @version    SVN: $Id: sfTestFunctionalBase.class.php 28641 2010-03-21 10:20:44Z fabien $
20
 */
21
abstract class sfTestFunctionalBase
22
{
23
  protected
24
    $testers       = array(),
25
    $blockTester   = null,
26
    $currentTester = null,
27
    $browser       = null;
28
 
29
  protected static
30
    $test = null;
31
 
32
  /**
33
   * Initializes the browser tester instance.
34
   *
35
   * @param sfBrowserBase $browser A sfBrowserBase instance
36
   * @param lime_test     $lime    A lime instance
37
   */
38
  public function __construct(sfBrowserBase $browser, lime_test $lime = null, $testers = array())
39
  {
40
    $this->browser = $browser;
41
 
42
    if (null === self::$test)
43
    {
44
      self::$test = null !== $lime ? $lime : new lime_test();
45
    }
46
 
47
    $this->setTesters(array_merge(array(
48
      'request'  => 'sfTesterRequest',
49
      'response' => 'sfTesterResponse',
50
      'user'     => 'sfTesterUser',
51
      'mailer'   => 'sfTesterMailer',
52
    ), $testers));
53
 
54
    // register our shutdown function
55
    register_shutdown_function(array($this, 'shutdown'));
56
 
57
    // register our error/exception handlers
58
    set_error_handler(array($this, 'handlePhpError'));
59
    set_exception_handler(array($this, 'handleException'));
60
  }
61
 
62
  /**
63
   * Returns the tester associated with the given name.
64
   *
65
   * @param string   $name The tester name
66
   *
67
   * @param sfTester A sfTester instance
68
   */
69
  public function with($name)
70
  {
71
    if (!isset($this->testers[$name]))
72
    {
73
      throw new InvalidArgumentException(sprintf('The "%s" tester does not exist.', $name));
74
    }
75
 
76
    if ($this->blockTester)
77
    {
78
      throw new LogicException(sprintf('You cannot nest tester blocks.'));
79
    }
80
 
81
    $this->currentTester = $this->testers[$name];
82
    $this->currentTester->initialize();
83
 
84
    return $this->currentTester;
85
  }
86
 
87
  /**
88
   * Begins a block of test for the current tester.
89
   *
90
   * @return sfTester The current sfTester instance
91
   */
92
  public function begin()
93
  {
94
    if (!$this->currentTester)
95
    {
96
      throw new LogicException(sprintf('You must call with() before beginning a tester block.'));
97
    }
98
 
99
    return $this->blockTester = $this->currentTester;
100
  }
101
 
102
  /**
103
   * End a block of test for the current tester.
104
   *
105
   * @return sfTestFunctionalBase
106
   */
107
  public function end()
108
  {
109
    if (null === $this->blockTester)
110
    {
111
      throw new LogicException(sprintf('There is no current tester block to end.'));
112
    }
113
 
114
    $this->blockTester = null;
115
 
116
    return $this;
117
  }
118
 
119
  /**
120
   * Sets the testers.
121
   *
122
   * @param array $testers An array of named testers
123
   */
124
  public function setTesters($testers)
125
  {
126
    foreach ($testers as $name => $tester)
127
    {
128
      $this->setTester($name, $tester);
129
    }
130
  }
131
 
132
  /**
133
   * Sets a tester.
134
   *
135
   * @param string          $name   The tester name
136
   * @param sfTester|string $tester A sfTester instance or a tester class name
137
   */
138
  public function setTester($name, $tester)
139
  {
140
    if (is_string($tester))
141
    {
142
      $tester = new $tester($this, self::$test);
143
    }
144
 
145
    if (!$tester instanceof sfTester)
146
    {
147
      throw new InvalidArgumentException(sprintf('The tester "%s" is not of class sfTester.', $name));
148
    }
149
 
150
    $this->testers[$name] = $tester;
151
  }
152
 
153
  /**
154
   * Shutdown function.
155
   *
156
   * @return void
157
   */
158
  public function shutdown()
159
  {
160
    $this->checkCurrentExceptionIsEmpty();
161
  }
162
 
163
  /**
164
   * Retrieves the lime_test instance.
165
   *
166
   * @return lime_test The lime_test instance
167
   */
168
  public function test()
169
  {
170
    return self::$test;
171
  }
172
 
173
  /**
174
   * Gets a uri.
175
   *
176
   * @param string $uri         The URI to fetch
177
   * @param array  $parameters  The Request parameters
178
   * @param bool   $changeStack  Change the browser history stack?
179
   *
180
   * @return sfTestFunctionalBase
181
   */
182
  public function get($uri, $parameters = array(), $changeStack = true)
183
  {
184
    return $this->call($uri, 'get', $parameters, $changeStack);
185
  }
186
 
187
  /**
188
   * Retrieves and checks an action.
189
   *
190
   * @param  string $module  Module name
191
   * @param  string $action  Action name
192
   * @param  string $url     Url
193
   * @param  string $code    The expected return status code
194
   *
195
   * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
196
   */
197
  public function getAndCheck($module, $action, $url = null, $code = 200)
198
  {
199
    return $this->
200
      get(null !== $url ? $url : sprintf('/%s/%s', $module, $action))->
201
      with('request')->begin()->
202
        isParameter('module', $module)->
203
        isParameter('action', $action)->
204
      end()->
205
      with('response')->isStatusCode($code)
206
    ;
207
  }
208
 
209
  /**
210
   * Posts a uri.
211
   *
212
   * @param string $uri         The URI to fetch
213
   * @param array  $parameters  The Request parameters
214
   * @param bool   $changeStack  Change the browser history stack?
215
   *
216
   * @return sfTestFunctionalBase
217
   */
218
  public function post($uri, $parameters = array(), $changeStack = true)
219
  {
220
    return $this->call($uri, 'post', $parameters, $changeStack);
221
  }
222
 
223
  /**
224
   * Calls a request.
225
   *
226
   * @param  string $uri          URI to be invoked
227
   * @param  string $method       HTTP method used
228
   * @param  array  $parameters   Additional parameters
229
   * @param  bool   $changeStack  If set to false ActionStack is not changed
230
   *
231
   * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
232
   */
233
  public function call($uri, $method = 'get', $parameters = array(), $changeStack = true)
234
  {
235
    $this->checkCurrentExceptionIsEmpty();
236
 
237
    $uri = $this->browser->fixUri($uri);
238
 
239
    $this->test()->comment(sprintf('%s %s', strtolower($method), $uri));
240
 
241
    foreach ($this->testers as $tester)
242
    {
243
      $tester->prepare();
244
    }
245
 
246
    $this->browser->call($uri, $method, $parameters, $changeStack);
247
 
248
    return $this;
249
  }
250
 
251
  /**
252
   * Simulates deselecting a checkbox or radiobutton.
253
   *
254
   * @param string  $name       The checkbox or radiobutton id, name or text
255
   *
256
   * @return sfTestFunctionalBase
257
   */
258
  public function deselect($name)
259
  {
260
    $this->browser->doSelect($name, false);
261
 
262
    return $this;
263
  }
264
 
265
  /**
266
   * Simulates selecting a checkbox or radiobutton.
267
   *
268
   * @param string  $name       The checkbox or radiobutton id, name or text
269
   *
270
   * @return sfTestFunctionalBase
271
   */
272
  public function select($name)
273
  {
274
    $this->browser->doSelect($name, true);
275
 
276
    return $this;
277
  }
278
 
279
  /**
280
   * Simulates a click on a link or button.
281
   *
282
   * @param string  $name       The link or button text
283
   * @param array   $arguments  The arguments to pass to the link
284
   * @param array   $options    An array of options
285
   *
286
   * @return sfTestFunctionalBase
287
   */
288
  public function click($name, $arguments = array(), $options = array())
289
  {
290
    if ($name instanceof DOMElement)
291
    {
292
      list($uri, $method, $parameters) = $this->doClickElement($name, $arguments, $options);
293
    }
294
    else
295
    {
296
      try
297
      {
298
        list($uri, $method, $parameters) = $this->doClick($name, $arguments, $options);
299
      }
300
      catch (InvalidArgumentException $e)
301
      {
302
        list($uri, $method, $parameters) = $this->doClickCssSelector($name, $arguments, $options);
303
      }
304
    }
305
 
306
    return $this->call($uri, $method, $parameters);
307
  }
308
 
309
  /**
310
   * Simulates the browser back button.
311
   *
312
   * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
313
   */
314
  public function back()
315
  {
316
    $this->test()->comment('back');
317
 
318
    $this->browser->back();
319
 
320
    return $this;
321
  }
322
 
323
  /**
324
   * Simulates the browser forward button.
325
   *
326
   * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
327
   */
328
  public function forward()
329
  {
330
    $this->test()->comment('forward');
331
 
332
    $this->browser->forward();
333
 
334
    return $this;
335
  }
336
 
337
  /**
338
   * Outputs an information message.
339
   *
340
   * @param string $message A message
341
   *
342
   * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
343
   */
344
  public function info($message)
345
  {
346
    $this->test()->info($message);
347
 
348
    return $this;
349
  }
350
 
351
  /**
352
   * Checks that the current response contains a given text.
353
   *
354
   * @param  string $uri   Uniform resource identifier
355
   * @param  string $text  Text in the response
356
   *
357
   * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
358
   */
359
  public function check($uri, $text = null)
360
  {
361
    $this->get($uri)->with('response')->isStatusCode();
362
 
363
    if ($text !== null)
364
    {
365
      $this->with('response')->contains($text);
366
    }
367
 
368
    return $this;
369
  }
370
 
371
  /**
372
   * Tests if an exception is thrown by the latest request.
373
   *
374
   * @param  string $class    Class name
375
   * @param  string $message  Message name
376
   *
377
   * @return sfTestFunctionalBase The current sfTestFunctionalBase instance
378
   */
379
  public function throwsException($class = null, $message = null)
380
  {
381
    $e = $this->browser->getCurrentException();
382
 
383
    if (null === $e)
384
    {
385
      $this->test()->fail('response returns an exception');
386
    }
387
    else
388
    {
389
      if (null !== $class)
390
      {
391
        $this->test()->ok($e instanceof $class, sprintf('response returns an exception of class "%s"', $class));
392
      }
393
 
394
      if (null !== $message && preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $message, $match))
395
      {
396
        if ($match[1] == '!')
397
        {
398
          $this->test()->unlike($e->getMessage(), substr($message, 1), sprintf('response exception message does not match regex "%s"', $message));
399
        }
400
        else
401
        {
402
          $this->test()->like($e->getMessage(), $message, sprintf('response exception message matches regex "%s"', $message));
403
        }
404
      }
405
      else if (null !== $message)
406
      {
407
        $this->test()->is($e->getMessage(), $message, sprintf('response exception message is "%s"', $message));
408
      }
409
    }
410
 
411
    $this->resetCurrentException();
412
 
413
    return $this;
414
  }
415
 
416
  /**
417
   * Triggers a test failure if an uncaught exception is present.
418
   *
419
   * @return  bool
420
   */
421
  public function checkCurrentExceptionIsEmpty()
422
  {
423
    if (false === ($empty = $this->browser->checkCurrentExceptionIsEmpty()))
424
    {
425
      $this->test()->fail(sprintf('last request threw an uncaught exception "%s: %s"', get_class($this->browser->getCurrentException()), $this->browser->getCurrentException()->getMessage()));
426
    }
427
 
428
    return $empty;
429
  }
430
 
431
  public function __call($method, $arguments)
432
  {
433
    $retval = call_user_func_array(array($this->browser, $method), $arguments);
434
 
435
    // fix the fluent interface
436
    return $retval === $this->browser ? $this : $retval;
437
  }
438
 
439
  /**
440
   * Error handler for the current test browser instance.
441
   *
442
   * @param mixed  $errno    Error number
443
   * @param string $errstr   Error message
444
   * @param string $errfile  Error file
445
   * @param mixed  $errline  Error line
446
   */
447
  static public function handlePhpError($errno, $errstr, $errfile, $errline)
448
  {
449
    if (($errno & error_reporting()) == 0)
450
    {
451
      return false;
452
    }
453
 
454
    $msg = sprintf('PHP sent a "%%s" error at %s line %s (%s)', $errfile, $errline, $errstr);
455
    switch ($errno)
456
    {
457
      case E_WARNING:
458
        $msg = sprintf($msg, 'warning');
459
        throw new RuntimeException($msg);
460
        break;
461
      case E_NOTICE:
462
        $msg = sprintf($msg, 'notice');
463
        throw new RuntimeException($msg);
464
        break;
465
      case E_STRICT:
466
        $msg = sprintf($msg, 'strict');
467
        throw new RuntimeException($msg);
468
        break;
469
      case E_RECOVERABLE_ERROR:
470
        $msg = sprintf($msg, 'catchable');
471
        throw new RuntimeException($msg);
472
        break;
473
    }
474
 
475
    return false;
476
  }
477
 
478
  /**
479
   * Exception handler for the current test browser instance.
480
   *
481
   * @param Exception $exception The exception
482
   */
483
  function handleException(Exception $exception)
484
  {
485
    $this->test()->error(sprintf('%s: %s', get_class($exception), $exception->getMessage()));
486
 
487
    $traceData = $exception->getTrace();
488
    array_unshift($traceData, array(
489
      'function' => '',
490
      'file'     => $exception->getFile() != null ? $exception->getFile() : 'n/a',
491
      'line'     => $exception->getLine() != null ? $exception->getLine() : 'n/a',
492
      'args'     => array(),
493
    ));
494
 
495
    $traces = array();
496
    $lineFormat = '  at %s%s%s() in %s line %s';
497
    for ($i = 0, $count = count($traceData); $i < $count; $i++)
498
    {
499
      $line = isset($traceData[$i]['line']) ? $traceData[$i]['line'] : 'n/a';
500
      $file = isset($traceData[$i]['file']) ? $traceData[$i]['file'] : 'n/a';
501
      $args = isset($traceData[$i]['args']) ? $traceData[$i]['args'] : array();
502
      $this->test()->error(sprintf($lineFormat,
503
        (isset($traceData[$i]['class']) ? $traceData[$i]['class'] : ''),
504
        (isset($traceData[$i]['type']) ? $traceData[$i]['type'] : ''),
505
        $traceData[$i]['function'],
506
        $file,
507
        $line
508
      ));
509
    }
510
 
511
    $this->test()->fail('An uncaught exception has been thrown.');
512
  }
513
}