Merge branch 'idle-irc-plugin' into irc-plugin

Conflicts:
	plugins/Irc/ircmanager.php
This commit is contained in:
Luke Fitzgerald 2010-08-12 12:00:42 -07:00
commit ed1ea2f086
21 changed files with 1823 additions and 603 deletions

View File

@ -60,6 +60,7 @@ class IrcPlugin extends ImPlugin {
public $channels = null; public $channels = null;
public $transporttype = null; public $transporttype = null;
public $encoding = null; public $encoding = null;
public $pinginterval = null;
public $regcheck = null; public $regcheck = null;
public $unregregexp = null; public $unregregexp = null;
@ -126,6 +127,7 @@ class IrcPlugin extends ImPlugin {
include_once $dir . '/'.strtolower($cls).'.php'; include_once $dir . '/'.strtolower($cls).'.php';
return false; return false;
case 'Fake_Irc': case 'Fake_Irc':
case 'Irc_waiting_message':
case 'ChannelResponseChannel': case 'ChannelResponseChannel':
include_once $dir . '/'. $cls .'.php'; include_once $dir . '/'. $cls .'.php';
return false; return false;
@ -150,6 +152,26 @@ class IrcPlugin extends ImPlugin {
return true; return true;
} }
/**
* Ensure the database table is present
*
*/
public function onCheckSchema() {
$schema = Schema::get();
// For storing messages while sessions become ready
$schema->ensureTable('irc_waiting_message',
array(new ColumnDef('id', 'integer', null,
false, 'PRI', null, null, true),
new ColumnDef('data', 'blob', null, false),
new ColumnDef('prioritise', 'tinyint', 1, false),
new ColumnDef('attempts', 'integer', null, false),
new ColumnDef('created', 'datetime', null, false),
new ColumnDef('claimed', 'datetime')));
return true;
}
/** /**
* Get a microid URI for the given screenname * Get a microid URI for the given screenname
* *
@ -171,7 +193,7 @@ class IrcPlugin extends ImPlugin {
$lines = explode("\n", $body); $lines = explode("\n", $body);
foreach ($lines as $line) { foreach ($lines as $line) {
$this->fake_irc->doPrivmsg($screenname, $line); $this->fake_irc->doPrivmsg($screenname, $line);
$this->enqueue_outgoing_raw(array('type' => 'message', 'data' => $this->fake_irc->would_be_sent)); $this->enqueue_outgoing_raw(array('type' => 'message', 'prioritise' => 0, 'data' => $this->fake_irc->would_be_sent));
} }
return true; return true;
} }
@ -297,6 +319,7 @@ class IrcPlugin extends ImPlugin {
$this->enqueue_outgoing_raw( $this->enqueue_outgoing_raw(
array( array(
'type' => 'nickcheck', 'type' => 'nickcheck',
'prioritise' => 1,
'data' => $this->fake_irc->would_be_sent, 'data' => $this->fake_irc->would_be_sent,
'nickdata' => 'nickdata' =>
array( array(
@ -337,6 +360,9 @@ class IrcPlugin extends ImPlugin {
if (!isset($this->encoding)) { if (!isset($this->encoding)) {
$this->encoding = 'UTF-8'; $this->encoding = 'UTF-8';
} }
if (!isset($this->pinginterval)) {
$this->pinginterval = 120;
}
if (!isset($this->regcheck)) { if (!isset($this->regcheck)) {
$this->regcheck = true; $this->regcheck = true;

View File

@ -0,0 +1,142 @@
<?php
/**
* Table Definition for irc_waiting_message
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Irc_waiting_message extends Memcached_DataObject {
public $__table = 'irc_waiting_message'; // table name
public $id; // int primary_key not_null auto_increment
public $data; // blob not_null
public $prioritise; // tinyint(1) not_null
public $attempts; // int not_null
public $created; // datetime() not_null
public $claimed; // datetime()
/* Static get */
public function staticGet($k, $v = null) {
return Memcached_DataObject::staticGet('Irc_waiting_message', $k, $v);
}
/**
* return table definition for DB_DataObject
*
* DB_DataObject needs to know something about the table to manipulate
* instances. This method provides all the DB_DataObject needs to know.
*
* @return array array of column definitions
*/
public function table() {
return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'data' => DB_DATAOBJECT_BLOB + DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'prioritise' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'created' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'claimed' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR);
}
/**
* return key definitions for DB_DataObject
*
* DB_DataObject needs to know about keys that the table has, since it
* won't appear in StatusNet's own keys list. In most cases, this will
* simply reference your keyTypes() function.
*
* @return array list of key field names
*/
public function keys() {
return array_keys($this->keyTypes());
}
/**
* return key definitions for Memcached_DataObject
*
* Our caching system uses the same key definitions, but uses a different
* method to get them. This key information is used to store and clear
* cached data, so be sure to list any key that will be used for static
* lookups.
*
* @return array associative array of key definitions, field name to type:
* 'K' for primary key: for compound keys, add an entry for each component;
* 'U' for unique keys: compound keys are not well supported here.
*/
public function keyTypes() {
return array('id' => 'K');
}
/**
* Magic formula for non-autoincrementing integer primary keys
*
* If a table has a single integer column as its primary key, DB_DataObject
* assumes that the column is auto-incrementing and makes a sequence table
* to do this incrementation. Since we don't need this for our class, we
* overload this method and return the magic formula that DB_DataObject needs.
*
* @return array magic three-false array that stops auto-incrementing.
*/
public function sequenceKey() {
return array(false, false, false);
}
/**
* Get the next item in the queue
*
* @return Irc_waiting_message Next message if there is one
*/
public static function top() {
$wm = new Irc_waiting_message();
$wm->orderBy('prioritise DESC, created');
$wm->whereAdd('claimed is null');
$wm->limit(1);
$cnt = $wm->find(true);
if ($cnt) {
# XXX: potential race condition
# can we force it to only update if claimed is still null
# (or old)?
common_log(LOG_INFO, 'claiming IRC waiting message id = ' . $wm->id);
$orig = clone($wm);
$wm->claimed = common_sql_now();
$result = $wm->update($orig);
if ($result) {
common_log(LOG_INFO, 'claim succeeded.');
return $wm;
} else {
common_log(LOG_INFO, 'claim failed.');
}
}
$wm = null;
return null;
}
/**
* Increment the attempts count
*
* @return void
* @throws Exception
*/
public function incAttempts() {
$orig = clone($this);
$this->attempts++;
$result = $this->update($orig);
if (!$result) {
throw Exception(sprintf(_m("Could not increment attempts count for %d"), $this->id));
}
}
/**
* Release a claimed item.
*/
public function releaseClaim() {
// DB_DataObject doesn't let us save nulls right now
$sql = sprintf("UPDATE irc_waiting_message SET claimed=NULL WHERE id=%d", $this->id);
$this->query($sql);
$this->claimed = null;
$this->encache();
}
}

View File

@ -23,6 +23,8 @@ nickservidentifyregexp: Override existing regexp matching request for identifica
channels: Channels for bot to idle in channels: Channels for bot to idle in
transporttype: Set to 'ssl' to enable SSL transporttype: Set to 'ssl' to enable SSL
encoding: Set to change encoding encoding: Set to change encoding
pinginterval: Set to change the number of seconds between pings (helps keep the connection open)
Defaults to 120 seconds
regcheck: Check user's nicknames are registered, enabled by default, set to false to disable regcheck: Check user's nicknames are registered, enabled by default, set to false to disable
regregexp: Override existing regexp matching response from NickServ if nick checked is registered. regregexp: Override existing regexp matching response from NickServ if nick checked is registered.
Must contain a capturing group catching the nick Must contain a capturing group catching the nick

View File

@ -141,9 +141,9 @@ class Phergie_Connection
{ {
if (empty($this->hostmask)) { if (empty($this->hostmask)) {
$this->hostmask = new Phergie_Hostmask( $this->hostmask = new Phergie_Hostmask(
$this->nick, $this->getNick(),
$this->username, $this->getUsername(),
$this->host $this->getHost()
); );
} }
@ -223,7 +223,7 @@ class Phergie_Connection
if (!in_array($this->transport, stream_get_transports())) { if (!in_array($this->transport, stream_get_transports())) {
throw new Phergie_Connection_Exception( throw new Phergie_Connection_Exception(
'Transport ' . $this->transport . ' is not supported', 'Transport ' . $this->transport . ' is not supported',
Phergie_Connection_Exception::TRANSPORT_NOT_SUPPORTED Phergie_Connection_Exception::ERR_TRANSPORT_NOT_SUPPORTED
); );
} }

View File

@ -110,7 +110,7 @@ class Phergie_Plugin_Cron extends Phergie_Plugin_Abstract
*/ */
public function onTick() public function onTick()
{ {
$now = time(); $time = time();
foreach ($this->callbacks as $key => &$callback) { foreach ($this->callbacks as $key => &$callback) {
$callbackString = $this->getCallbackString($callback); $callbackString = $this->getCallbackString($callback);

View File

@ -98,6 +98,12 @@ class Phergie_Plugin_Handler implements IteratorAggregate, Countable
$this->paths = array(); $this->paths = array();
$this->autoload = false; $this->autoload = false;
if (!empty($config['plugins.paths'])) {
foreach ($config['plugins.paths'] as $dir => $prefix) {
$this->addPath($dir, $prefix);
}
}
$this->addPath(dirname(__FILE__), 'Phergie_Plugin_'); $this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
} }
@ -134,6 +140,7 @@ class Phergie_Plugin_Handler implements IteratorAggregate, Countable
* Returns metadata corresponding to a specified plugin. * Returns metadata corresponding to a specified plugin.
* *
* @param string $plugin Short name of the plugin class * @param string $plugin Short name of the plugin class
*
* @throws Phergie_Plugin_Exception Class file can't be found * @throws Phergie_Plugin_Exception Class file can't be found
* *
* @return array|boolean Associative array containing the path to the * @return array|boolean Associative array containing the path to the
@ -142,7 +149,7 @@ class Phergie_Plugin_Handler implements IteratorAggregate, Countable
*/ */
public function getPluginInfo($plugin) public function getPluginInfo($plugin)
{ {
foreach (array_reverse($this->paths) as $path) { foreach (array_reverse($this->paths) as $path) {
$file = $path['path'] . $plugin . '.php'; $file = $path['path'] . $plugin . '.php';
if (file_exists($file)) { if (file_exists($file)) {
$path = array( $path = array(
@ -444,15 +451,21 @@ class Phergie_Plugin_Handler implements IteratorAggregate, Countable
$valid = true; $valid = true;
try { try {
$error_reporting = error_reporting(0); // ignore autoloader errors
$r = new ReflectionClass($class); $r = new ReflectionClass($class);
$valid = $r->isSubclassOf('FilterIterator'); error_reporting($error_reporting);
if (!$r->isSubclassOf('FilterIterator')) {
$message = 'Class ' . $class . ' is not a subclass of FilterIterator';
$valid = false;
}
} catch (ReflectionException $e) { } catch (ReflectionException $e) {
$message = $e->getMessage();
$valid = false; $valid = false;
} }
if (!$valid) { if (!$valid) {
throw new Phergie_Plugin_Exception( throw new Phergie_Plugin_Exception(
$e->getMessage(), $message,
Phergie_Plugin_Exception::ERR_INVALID_ITERATOR_CLASS Phergie_Plugin_Exception::ERR_INVALID_ITERATOR_CLASS
); );
} }

View File

@ -31,7 +31,6 @@
* @uses extension PDO * @uses extension PDO
* @uses extension pdo_sqlite * @uses extension pdo_sqlite
* @uses Phergie_Plugin_Command pear.phergie.org * @uses Phergie_Plugin_Command pear.phergie.org
* @uses Phergie_Plugin_Message pear.phergie.org
*/ */
class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract
{ {
@ -94,11 +93,16 @@ class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract
{ {
$plugins = $this->getPluginHandler(); $plugins = $this->getPluginHandler();
$plugins->getPlugin('Command'); $plugins->getPlugin('Command');
$plugins->getPlugin('Message'); $this->getDb();
}
$file = dirname(__FILE__) . '/Karma/karma.db';
$this->db = new PDO('sqlite:' . $file);
/**
* Initializes prepared statements used by the plugin.
*
* @return void
*/
protected function initializePreparedStatements()
{
$this->fetchKarma = $this->db->prepare(' $this->fetchKarma = $this->db->prepare('
SELECT karma SELECT karma
FROM karmas FROM karmas
@ -139,6 +143,36 @@ class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract
'); ');
} }
/**
* Returns a connection to the plugin database, initializing one if none
* is explicitly set.
*
* @return PDO Database connection
*/
public function getDb()
{
if (empty($this->db)) {
$this->db = new PDO('sqlite:' . dirname(__FILE__) . '/Karma/karma.db');
$this->initializePreparedStatements();
}
return $this->db;
}
/**
* Sets the connection to the plugin database, mainly intended for unit
* testing.
*
* @param PDO $db Database connection
*
* @return Phergie_Plugin_Karma Provides a fluent interface
*/
public function setDb(PDO $db)
{
$this->db = $db;
$this->initializePreparedStatements();
return $this;
}
/** /**
* Get the canonical form of a given term. * Get the canonical form of a given term.
* *
@ -228,15 +262,11 @@ REGEX;
$source = $this->getEvent()->getSource(); $source = $this->getEvent()->getSource();
$nick = $this->getEvent()->getNick(); $nick = $this->getEvent()->getNick();
if (empty($term)) {
return;
}
$canonicalTerm = $this->getCanonicalTerm($term); $canonicalTerm = $this->getCanonicalTerm($term);
$fixedKarma = $this->fetchFixedKarma($canonicalTerm); $fixedKarma = $this->fetchFixedKarma($canonicalTerm);
if ($fixedKarma) { if ($fixedKarma) {
$message = $nick . ': ' . $term . ' ' . $fixedKarma . '.'; $message = $nick . ': ' . $term . ' ' . $fixedKarma;
$this->doPrivmsg($source, $message); $this->doPrivmsg($source, $message);
return; return;
} }
@ -302,33 +332,29 @@ REGEX;
$fixedKarma0 = $this->fetchFixedKarma($canonicalTerm0); $fixedKarma0 = $this->fetchFixedKarma($canonicalTerm0);
$fixedKarma1 = $this->fetchFixedKarma($canonicalTerm1); $fixedKarma1 = $this->fetchFixedKarma($canonicalTerm1);
if ($fixedKarma0 if ($fixedKarma0 || $fixedKarma1) {
|| $fixedKarma1
|| empty($canonicalTerm0)
|| empty($canonicalTerm1)
) {
return; return;
} }
if ($canonicalTerm0 == 'everything') { if ($canonicalTerm0 == 'everything') {
$change = $method == '<' ? '++' : '--'; $change = $method == '<' ? '++' : '--';
$this->modifyKarma($canonicalTerm1, $change);
$karma0 = 0; $karma0 = 0;
$karma1 = $this->fetchKarma($canonicalTerm1); $karma1 = $this->modifyKarma($canonicalTerm1, $change);
} elseif ($canonicalTerm1 == 'everything') { } elseif ($canonicalTerm1 == 'everything') {
$change = $method == '<' ? '--' : '++'; $change = $method == '<' ? '--' : '++';
$this->modifyKarma($canonicalTerm0, $change); $karma0 = $this->modifyKarma($canonicalTerm0, $change);
$karma0 = $this->fetchKarma($canonicalTerm1);
$karma1 = 0; $karma1 = 0;
} else { } else {
$karma0 = $this->fetchKarma($canonicalTerm0); $karma0 = $this->fetchKarma($canonicalTerm0);
$karma1 = $this->fetchKarma($canonicalTerm1); $karma1 = $this->fetchKarma($canonicalTerm1);
} }
if (($method == '<' // Combining the first and second branches here causes an odd
&& $karma0 < $karma1) // single-line lapse in code coverage, but the lapse disappears if
|| ($method == '>' // they're separated
&& $karma0 > $karma1)) { if ($method == '<' && $karma0 < $karma1) {
$replies = $this->fetchPositiveAnswer;
} elseif ($method == '>' && $karma0 > $karma1) {
$replies = $this->fetchPositiveAnswer; $replies = $this->fetchPositiveAnswer;
} else { } else {
$replies = $this->fetchNegativeAnswer; $replies = $this->fetchNegativeAnswer;
@ -356,14 +382,10 @@ REGEX;
* @param string $term Term to modify * @param string $term Term to modify
* @param string $action Karma action (either ++ or --) * @param string $action Karma action (either ++ or --)
* *
* @return void * @return int Modified karma rating
*/ */
protected function modifyKarma($term, $action) protected function modifyKarma($term, $action)
{ {
if (empty($term)) {
return;
}
$karma = $this->fetchKarma($term); $karma = $this->fetchKarma($term);
if ($karma !== false) { if ($karma !== false) {
$statement = $this->updateKarma; $statement = $this->updateKarma;
@ -378,6 +400,8 @@ REGEX;
':karma' => $karma ':karma' => $karma
); );
$statement->execute($args); $statement->execute($args);
return $karma;
} }
/** /**

View File

@ -52,14 +52,14 @@ class Phergie_Plugin_Message extends Phergie_Plugin_Abstract
$}ix $}ix
REGEX; REGEX;
return !$event->isInChannel() return !$event->isInChannel()
|| preg_match($targetPattern, $event->getText()) > 0; || preg_match($targetPattern, $event->getText()) > 0;
} }
/** /**
* Allow for prefix and bot name aware extraction of a message * Allow for prefix and bot name aware extraction of a message
* *
* @return string|bool $message The message, which is possibly targeted at the * @return string|bool $message The message, which is possibly targeted at the
* bot or false if a prefix requirement failed * bot or false if a prefix requirement failed
*/ */
public function getMessage() public function getMessage()

View File

@ -54,10 +54,27 @@ class Phergie_Plugin_Reload extends Phergie_Plugin_Abstract
{ {
$plugin = ucfirst($plugin); $plugin = ucfirst($plugin);
$evalClass = true;
if (strpos($plugin, ' ') !== false) {
$args = explode(' ', $plugin);
$plugin = $args[0];
if (strtolower($args[1]) == 'force') {
$evalClass = false;
}
}
if (!$this->plugins->hasPlugin($plugin)) { if (!$this->plugins->hasPlugin($plugin)) {
echo 'DEBUG(Reload): ' . ucfirst($plugin) . ' is not loaded yet, loading', PHP_EOL; echo 'DEBUG(Reload): ' . ucfirst($plugin) . ' is not loaded yet, loading', PHP_EOL;
$this->plugins->getPlugin($plugin); try {
$this->plugins->command->populateMethodCache(); $this->plugins->getPlugin($plugin);
$this->plugins->command->populateMethodCache();
} catch (Phergie_Plugin_Exception $e) {
if ($e->getCode() == Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND) {
echo 'DEBUG(Reload): ', $e->getMessage(), PHP_EOL;
} else {
throw $e;
}
}
return; return;
} }
@ -75,17 +92,19 @@ class Phergie_Plugin_Reload extends Phergie_Plugin_Abstract
$newClass = $class . '_' . sha1($contents); $newClass = $class . '_' . sha1($contents);
if (class_exists($newClass, false)) { if (class_exists($newClass, false)) {
echo 'DEBUG(Reload): Class ', $class, ' has not changed since last reload', PHP_EOL; if ($evalClass == true) {
return; echo 'DEBUG(Reload): Class ', $class, ' has not changed since last reload', PHP_EOL;
return;
}
} else {
$contents = preg_replace(
array('/^<\?(?:php)?/', '/class\s+' . $class . '/i'),
array('', 'class ' . $newClass),
$contents
);
eval($contents);
} }
$contents = preg_replace(
array('/^<\?(?:php)?/', '/class\s+' . $class . '/i'),
array('', 'class ' . $newClass),
$contents
);
eval($contents);
$instance = new $newClass; $instance = new $newClass;
$instance->setName($plugin); $instance->setName($plugin);
$instance->setEvent($this->event); $instance->setEvent($this->event);

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* Phergie * Phergie
* *
* PHP version 5 * PHP version 5
* *
@ -11,7 +11,7 @@
* It is also available through the world-wide-web at this URL: * It is also available through the world-wide-web at this URL:
* http://phergie.org/license * http://phergie.org/license
* *
* @category Phergie * @category Phergie
* @package Phergie_Plugin_TerryChay * @package Phergie_Plugin_TerryChay
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org) * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
@ -24,7 +24,7 @@
* either confirmation of correctly spelled words or potential correct * either confirmation of correctly spelled words or potential correct
* spellings for misspelled words. * spellings for misspelled words.
* *
* @category Phergie * @category Phergie
* @package Phergie_Plugin_SpellCheck * @package Phergie_Plugin_SpellCheck
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
@ -34,7 +34,6 @@
*/ */
class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract
{ {
/** /**
* Spell check dictionary handler * Spell check dictionary handler
* *
@ -65,7 +64,7 @@ class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract
} }
$this->plugins->getPlugin('Command'); $this->plugins->getPlugin('Command');
set_error_handler(array($this, 'loadDictionaryError')); set_error_handler(array($this, 'loadDictionaryError'));
$this->pspell = pspell_new($this->getConfig('spellcheck.lang')); $this->pspell = pspell_new($this->getConfig('spellcheck.lang'));
restore_error_handler(); restore_error_handler();
@ -86,11 +85,11 @@ class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract
$target = $this->event->getNick(); $target = $this->event->getNick();
$message = $target . ': The word "' . $word; $message = $target . ': The word "' . $word;
$message .= '" seems to be spelt correctly.'; $message .= '" seems to be spelled correctly.';
if (!pspell_check($this->pspell, $word)) { if (!pspell_check($this->pspell, $word)) {
$suggestions = pspell_suggest($this->pspell, $word); $suggestions = pspell_suggest($this->pspell, $word);
$message = $target; $message = $target;
$message .= ': I could not find any suggestions for "' . $word . '".'; $message .= ': I could not find any suggestions for "' . $word . '".';
if (!empty($suggestions)) { if (!empty($suggestions)) {
$suggestions = array_splice($suggestions, 0, $this->limit); $suggestions = array_splice($suggestions, 0, $this->limit);
@ -98,7 +97,7 @@ class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract
$message .= $word . '": ' . implode(', ', $suggestions) . '.'; $message .= $word . '": ' . implode(', ', $suggestions) . '.';
} }
} }
$this->doPrivmsg($source, $message); $this->doPrivmsg($source, $message);
} }
@ -116,5 +115,4 @@ class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract
{ {
$this->fail($errstr); $this->fail($errstr);
} }
} }

View File

@ -41,6 +41,13 @@ class Phergie_Plugin_Statusnet extends Phergie_Plugin_Abstract {
*/ */
protected $regCallback; protected $regCallback;
/**
* Connection established callback details
*
* @var array
*/
protected $connectedCallback;
/** /**
* Load callback from config * Load callback from config
*/ */
@ -59,6 +66,13 @@ class Phergie_Plugin_Statusnet extends Phergie_Plugin_Abstract {
$this->regCallback = NULL; $this->regCallback = NULL;
} }
$connectedCallback = $this->config['statusnet.connectedcallback'];
if (is_callable($connectedCallback)) {
$this->connectedCallback = $connectedCallback;
} else {
$this->connectedCallback = NULL;
}
$this->unregRegexp = $this->getConfig('statusnet.unregregexp', '/\x02(.*?)\x02 (?:isn\'t|is not) registered/i'); $this->unregRegexp = $this->getConfig('statusnet.unregregexp', '/\x02(.*?)\x02 (?:isn\'t|is not) registered/i');
$this->regRegexp = $this->getConfig('statusnet.regregexp', '/(?:\A|\x02)(\w+?)\x02? (?:\(account|is \w+?\z)/i'); $this->regRegexp = $this->getConfig('statusnet.regregexp', '/(?:\A|\x02)(\w+?)\x02? (?:\(account|is \w+?\z)/i');
} }
@ -110,4 +124,20 @@ class Phergie_Plugin_Statusnet extends Phergie_Plugin_Abstract {
} }
} }
} }
/**
* Intercepts the end of the "message of the day" response and tells
* StatusNet we're connected
*
* @return void
*/
public function onResponse() {
switch ($this->getEvent()->getCode()) {
case Phergie_Event_Response::RPL_ENDOFMOTD:
case Phergie_Event_Response::ERR_NOMOTD:
if ($this->connectedCallback !== NULL) {
call_user_func($this->connectedCallback);
}
}
}
} }

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* Phergie * Phergie
* *
* PHP version 5 * PHP version 5
* *
@ -11,7 +11,7 @@
* It is also available through the world-wide-web at this URL: * It is also available through the world-wide-web at this URL:
* http://phergie.org/license * http://phergie.org/license
* *
* @category Phergie * @category Phergie
* @package Phergie_Plugin_TerryChay * @package Phergie_Plugin_TerryChay
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org) * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
@ -21,9 +21,9 @@
/** /**
* Parses incoming messages for the words "Terry Chay" or tychay and responds * Parses incoming messages for the words "Terry Chay" or tychay and responds
* with a random Terry fact retrieved from the Chayism web service. * with a random Terry fact retrieved from the Chayism web service.
* *
* @category Phergie * @category Phergie
* @package Phergie_Plugin_TerryChay * @package Phergie_Plugin_TerryChay
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
@ -53,21 +53,25 @@ class Phergie_Plugin_TerryChay extends Phergie_Plugin_Abstract
*/ */
public function onLoad() public function onLoad()
{ {
$this->http = $this->getPluginHandler()->getPlugin('Http'); $this->getPluginHandler()->getPlugin('Http');
} }
/** /**
* Fetches a chayism. * Fetches a chayism.
* *
* @return string|bool Fetched chayism or FALSE if the operation failed * @return string|bool Fetched chayism or FALSE if the operation failed
*/ */
public function getChayism() public function getChayism()
{ {
return $this->http->get(self::URL)->getContent(); return $this
->getPluginHandler()
->getPlugin('Http')
->get(self::URL)
->getContent();
} }
/** /**
* Parses incoming messages for "Terry Chay" and related variations and * Parses incoming messages for "Terry Chay" and related variations and
* responds with a chayism. * responds with a chayism.
* *
* @return void * @return void
@ -77,33 +81,14 @@ class Phergie_Plugin_TerryChay extends Phergie_Plugin_Abstract
$event = $this->getEvent(); $event = $this->getEvent();
$source = $event->getSource(); $source = $event->getSource();
$message = $event->getText(); $message = $event->getText();
$pattern $pattern
= '{^(' . preg_quote($this->getConfig('command.prefix')) . = '{^(' . preg_quote($this->getConfig('command.prefix')) .
'\s*)?.*(terry\s+chay|tychay)}ix'; '\s*)?.*(terry\s+chay|tychay)}ix';
if (preg_match($pattern, $message) if (preg_match($pattern, $message)) {
&& $fact = $this->getChayism() if($fact = $this->getChayism()) {
) { $this->doPrivmsg($source, 'Fact: ' . $fact);
$this->doPrivmsg($source, 'Fact: ' . $fact); }
}
}
/**
* Parses incoming CTCP request for "Terry Chay" and related variations
* and responds with a chayism.
*
* @return void
*/
public function onCtcp()
{
$event = $this->getEvent();
$source = $event->getSource();
$ctcp = $event->getArgument(1);
if (preg_match('({terry[\s_+-]*chay}|tychay)ix', $ctcp)
&& $fact = $this->getChayism()
) {
$this->doCtcpReply($source, 'TERRYCHAY', $fact);
} }
} }
} }

View File

@ -0,0 +1,262 @@
<?php
/**
* Phergie
*
* PHP version 5
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.
* It is also available through the world-wide-web at this URL:
* http://phergie.org/license
*
* @category Phergie
* @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Tests
*/
/**
* Unit test suite for Pherge_Connection.
*
* @category Phergie
* @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Tests
*/
class Phergie_ConnectionTest extends PHPUnit_Framework_TestCase
{
/**
* Associative array containing an option-to-value mapping
*
* @var array
*/
private $options = array(
'host' => 'example.com',
'port' => 4080,
'transport' => 'udp',
'encoding' => 'ASCII',
'nick' => 'MyNick',
'username' => 'MyUsername',
'realname' => 'MyRealName',
'password' => 'MyPassword',
);
/**
* Data provider for testGetOptionReturnsDefault().
*
* @return array Enumerated array of enumerated arrays each containing a
* set of parameters for a single call to
* testGetOptionReturnsDefault()
*/
public function dataProviderTestGetOptionReturnsDefault()
{
return array(
array('transport', 'tcp'),
array('encoding', 'ISO-8859-1'),
array('port', 6667),
array('password', null),
);
}
/**
* Tests that a default values are used for some options.
*
* @param string $option Name of the option with a default value
* @param mixed $value Default value of the option
*
* @return void
* @dataProvider dataProviderTestGetOptionReturnsDefault
*/
public function testGetOptionReturnsDefault($option, $value)
{
$connection = new Phergie_Connection;
$this->assertEquals($value, $connection->{'get' . ucfirst($option)}());
}
/**
* Tests that a default encoding is used if one isn't specified.
*
* @return void
*/
public function testGetEncodingReturnsDefault()
{
$connection = new Phergie_Connection;
$this->assertEquals('ISO-8859-1', $connection->getEncoding());
}
/**
* Tests that options can be set via the constructor.
*
* @return void
*/
public function testSetOptionsViaConstructor()
{
$connection = new Phergie_Connection($this->options);
foreach ($this->options as $key => $value) {
$this->assertEquals($value, $connection->{'get' . ucfirst($key)}());
}
}
/**
* Data provider for testGetHostmaskMissingDataGeneratesException().
*
* @return array Enumerated array of enumerated arrays each containing a
* set of parameters for a single call to
* testGetHostmaskMissingDataGeneratesException()
*/
public function dataProviderTestGetHostmaskMissingDataGeneratesException()
{
return array(
array(null, $this->options['username'], $this->options['host']),
array($this->options['nick'], null, $this->options['host']),
array($this->options['nick'], $this->options['username'], null),
);
}
/**
* Tests that attempting to retrieve a hostmask without option values
* for all of its constituents generates an exception.
*
* @param string $nick Bot nick
* @param string $username Bot username
* @param string $host Server hostname
*
* @return void
* @dataProvider dataProviderTestGetHostmaskMissingDataGeneratesException
*/
public function testGetHostmaskMissingDataGeneratesException($nick, $username, $host)
{
$options = array(
'nick' => $nick,
'username' => $username,
'host' => $host,
);
$connection = new Phergie_Connection($options);
try {
$hostmask = $connection->getHostmask();
$this->fail('Expected exception was not thrown');
} catch (Phergie_Connection_Exception $e) {
return;
} catch (Exception $e) {
$this->fail('Unexpected exception was thrown');
}
}
/**
* Tests that attempting to retrieve a hostmask with all required
* options is successful.
*
* @return void
*/
public function testGetHostmaskWithValidData()
{
$options = array(
'nick' => 'MyNick',
'username' => 'MyUsername',
'host' => 'example.com'
);
$connection = new Phergie_Connection($options);
$hostmask = $connection->getHostmask();
$this->assertType('Phergie_Hostmask', $hostmask);
}
/**
* Data provider for testGetRequiredOptionsWithoutValuesSet().
*
* @return array Enumerated array of enumerated arrays each containing a
* set of parameters for a single call to
* testGetRequiredOptionsWithoutValuesSet()
*/
public function dataProviderTestGetRequiredOptionsWithoutValuesSet()
{
return array(
array('host'),
array('nick'),
array('username'),
array('realname'),
);
}
/**
* Tests that attempting to retrieve values of required options when no
* values are set results in an exception.
*
* @param string $option Option name
*
* @return void
* @dataProvider dataProviderTestGetRequiredOptionsWithoutValuesSet
*/
public function testGetRequiredOptionsWithoutValuesSet($option)
{
try {
$connection = new Phergie_Connection;
$value = $connection->{'get' . ucfirst($option)}();
$this->fail('Expected exception was not thrown');
} catch (Phergie_Connection_Exception $e) {
return;
} catch (Exception $e) {
$this->fail('Unexpected exception was thrown');
}
}
/**
* Tests that attempting to set an invalid value for the transport
* results in an exception.
*
* @return void
*/
public function testSetTransportWithInvalidValue()
{
$connection = new Phergie_Connection;
try {
$connection->setTransport('blah');
$this->fail('Expected exception was not thrown');
} catch (Phergie_Connection_Exception $e) {
return;
} catch (Exception $e) {
$this->fail('Unexpected exception was thrown');
}
}
/**
* Tests that attempting to set an invalid value for the encoding
* results in an exception.
*
* @return void
*/
public function testSetEncodingWithInvalidValue()
{
$connection = new Phergie_Connection;
try {
$connection->setEncoding('blah');
$this->fail('Expected exception was not thrown');
} catch (Phergie_Connection_Exception $e) {
return;
} catch (Exception $e) {
$this->fail('Unexpected exception was thrown');
}
}
/**
* Tests that options can be set collectively after the connection is
* instantiated.
*
* @return void
*/
public function testSetOptions()
{
$connection = new Phergie_Connection;
$connection->setOptions($this->options);
foreach ($this->options as $key => $value) {
$this->assertEquals($value, $connection->{'get' . ucfirst($key)}());
}
}
}

View File

@ -108,6 +108,77 @@ class Phergie_Plugin_HandlerTest extends PHPUnit_Framework_TestCase
); );
} }
/**
* Tests that a default iterator is returned if none is explicitly set.
*
* @return void
*/
public function testGetIteratorReturnsDefault()
{
$this->assertType(
'Phergie_Plugin_Iterator',
$this->handler->getIterator()
);
}
/**
* Tests the ability to change the handler's iterator class when a valid
* class is specified.
*
* @return void
*/
public function testSetIteratorClassWithValidClass()
{
eval('
class DummyIterator extends FilterIterator {
public function accept() {
return true;
}
}
');
$this->handler->setIteratorClass('DummyIterator');
$this->assertType(
'DummyIterator',
$this->handler->getIterator()
);
}
/**
* Tests that a failure occurs when a nonexistent iterator class is
* specified.
*
* @return void
*/
public function testSetIteratorClassWithNonexistentClass()
{
try {
$this->handler->setIteratorClass('FooIterator');
$this->fail('Expected exception was not thrown');
} catch (Phergie_Plugin_Exception $e) {
return;
}
$this->fail('Unexpected exception was thrown');
}
/**
* Tests that a failure occurs when a class that is not a subclass of
* FilterIterator is specified.
*
* @return void
*/
public function testSetIteratorClassWithNonFilterIteratorClass()
{
try {
$this->handler->setIteratorClass('ArrayIterator');
$this->fail('Expected exception was not thrown');
} catch (Phergie_Plugin_Exception $e) {
return;
}
$this->fail('Unexpected exception was thrown');
}
/** /**
* Tests countability of the plugin handler. * Tests countability of the plugin handler.
* *
@ -714,23 +785,53 @@ class Phergie_Plugin_HandlerTest extends PHPUnit_Framework_TestCase
} }
/** /**
* Tests the plugin receiving and using a predefined iterator instance. * Tests that multiple plugin iterators can be used concurrently.
* *
* @depends testGetPlugins
* @return void * @return void
*/ */
public function testSetIterator() public function testUseMultiplePluginIteratorsConcurrently()
{ {
$plugin = $this->getMockPlugin('TestPlugin'); $plugin1 = $this->getMockPlugin('TestPlugin1');
$this->handler->addPlugin($plugin); $this->handler->addPlugin($plugin1);
$plugins = $this->handler->getPlugins();
$iterator = new ArrayIterator($plugins); $plugin2 = $this->getMockPlugin('TestPlugin2');
$this->handler->setIterator($iterator); $this->handler->addPlugin($plugin2);
$this->assertSame($this->handler->getIterator(), $iterator);
$iterated = array(); $iterator1 = $this->handler->getIterator();
foreach ($this->handler as $plugin) { $iterator1->next();
$iterated[strtolower($plugin->getName())] = $plugin; $this->assertSame($plugin2, $iterator1->current());
}
$this->assertEquals($iterated, $plugins); $iterator2 = $this->handler->getIterator();
$this->assertSame($plugin1, $iterator2->current());
}
/**
* Tests adding plugin paths via configuration.
*
* @return void
*/
public function testAddPluginPathsViaConfiguration()
{
$dir = dirname(__FILE__);
$prefix = 'Phergie_Plugin_';
$paths = array($dir => $prefix);
$this->config
->expects($this->any())
->method('offsetExists')
->will($this->returnValue(true));
$this->config
->expects($this->any())
->method('offsetGet')
->will($this->returnValue($paths));
// Reinitialize the handler so the configuration change takes effect
// within the constructor
$this->handler = new Phergie_Plugin_Handler(
$this->config,
$this->events
);
$this->handler->setAutoload(true);
$this->handler->getPlugin('Mock');
} }
} }

View File

@ -0,0 +1,335 @@
<?php
/**
* Phergie
*
* PHP version 5
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.
* It is also available through the world-wide-web at this URL:
* http://phergie.org/license
*
* @category Phergie
* @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Tests
*/
/**
* Unit test suite for Pherge_Plugin_Karma.
*
* @category Phergie
* @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Tests
*/
class Phergie_Plugin_KarmaTest extends Phergie_Plugin_TestCase
{
/**
* Skips tests if the SQLite PDO driver is not available.
*
* @return void
*/
public function setUp()
{
if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
$this->markTestSkipped('PDO or pdo_sqlite extension is required');
}
parent::setUp();
}
/**
* Configures the plugin to use a temporary copy of the database.
*
* @return PDO Connection to the temporary database
*/
private function createMockDatabase()
{
$dbPath = $this->getPluginsPath('Karma/karma.db');
$db = $this->getMockDatabase($dbPath);
$this->plugin->setDb($db);
return $db;
}
/**
* Tests the requirement of the Command plugin.
*
* @return void
*/
public function testRequiresCommandPlugin()
{
$this->assertRequiresPlugin('Command');
$this->plugin->onLoad();
}
/**
* Initiates a karma event with a specified term.
*
* @param string $term Karma term
*
* @return Phergie_Event_Request Initiated mock event
*/
private function initiateKarmaEvent($term)
{
$args = array(
'receiver' => $this->source,
'text' => 'karma ' . $term
);
$event = $this->getMockEvent('privmsg', $args);
$this->plugin->setEvent($event);
return $event;
}
/**
* Checks for an expected karma response.
*
* @param Phergie_Event_Request $event Event containing the karma
* request
* @param string $term Karma term
* @param string $response Portion of the response
* message following the term
* from the original event
*
* @return void
*/
private function checkForKarmaResponse($event, $term, $response)
{
$text = $event->getNick() . ': ' . $response;
$this->assertEmitsEvent('privmsg', array($event->getSource(), $text));
$this->plugin->onCommandKarma($term);
}
/**
* Tests that a default database is used when none is specified.
*
* @return void
*/
public function testGetDb()
{
$db = $this->plugin->getDb();
$this->assertType('PDO', $db);
}
/**
* Tests specifying a custom database for the plugin to use.
*
* @return void
*/
public function testSetDb()
{
$db = $this->createMockDatabase();
$this->assertSame($db, $this->plugin->getDb());
}
/**
* Tests that issuing the karma command with an unknown term returns a
* neutral rating.
*
* @return void
*/
public function testKarmaCommandOnUnknownTerm()
{
$term = 'foo';
$this->createMockDatabase();
$event = $this->initiateKarmaEvent($term);
$this->checkForKarmaResponse($event, $term, $term . ' has neutral karma.');
}
/**
* Tests that issuing the karma command with the term "me" returns the
* the karma rating for the initiating user.
*
* @return void
*/
public function testKarmaCommandOnUser()
{
$term = 'me';
$this->createMockDatabase();
$event = $this->initiateKarmaEvent($term);
$this->checkForKarmaResponse($event, $term, 'You have neutral karma.');
}
/**
* Tests that issuing the karma command with a term that has a fixed
* karma rating results in that rating being returned.
*
* @return void
*/
public function testKarmaCommandWithFixedKarmaTerm()
{
$term = 'phergie';
$this->createMockDatabase();
$event = $this->initiateKarmaEvent($term);
$this->checkForKarmaResponse($event, $term, 'phergie has karma of awesome.');
}
/**
* Supporting method that tests the result of a karma term rating change.
*
* @param string $term Karma term for which the rating is being
* changed
* @param string $operation ++ or --
* @param int $karma Expected karma rating after the change is
* applied
*/
private function checkForKarmaRatingChange($term, $operation, $karma)
{
$args = array(
'receiver' => $this->source,
'text' => $term . $operation
);
$event = $this->getMockEvent('privmsg', $args);
$this->plugin->setEvent($event);
$this->plugin->onPrivmsg();
$event = $this->initiateKarmaEvent($term);
$this->checkForKarmaResponse($event, $term, $term . ' has karma of ' . $karma . '.');
}
/**
* Tests incrementing the karma rating of a new term.
*
* @return void
*/
public function testIncrementingKarmaRating()
{
$this->createMockDatabase();
$this->checkForKarmaRatingChange('foo', '++', 1);
}
/**
* Tests decrementing the karma rating of a new term.
*
* @return void
*/
public function testDecrementingKarmaRating()
{
$this->createMockDatabase();
$this->checkForKarmaRatingChange('foo', '--', -1);
}
/**
* Tests modifying the karma rating of an existing term.
*
* @return void
*/
public function testChangingExistingKarmaRating()
{
$term = 'foo';
$this->createMockDatabase();
$this->checkForKarmaRatingChange($term, '++', 1);
$this->checkForKarmaRatingChange($term, '++', 2);
}
/**
* Tests resetting the karma rating of an existing term to 0.
*
* @return void
*/
public function testResettingExistingKarmaRating()
{
$term = 'foo';
$this->createMockDatabase();
$this->checkForKarmaRatingChange($term, '++', 1);
$this->plugin->onCommandReincarnate($term);
$event = $this->initiateKarmaEvent($term);
$this->checkForKarmaResponse($event, $term, $term . ' has neutral karma.');
}
/**
* Data provider for testKarmaComparisons().
*
* @return array Enumerated array of enumerated arrays each containing a
* set of parameter values for a single call to
* testKarmaComparisons()
*/
public function dataProviderTestKarmaComparisons()
{
$term1 = 'foo';
$term2 = 'bar';
$positive = 'True that.';
$negative = 'No sir, not at all.';
return array(
array($term1, $term2, 1, 0, '>', $positive),
array($term1, $term2, 0, 1, '>', $negative),
array($term1, $term2, 1, 1, '>', $negative),
array($term1, $term2, 1, 0, '<', $negative),
array($term1, $term2, 0, 1, '<', $positive),
array($term1, $term2, 1, 1, '<', $negative),
array($term1, 'phergie', 1, 0, '>', $positive),
array('phergie', $term2, 0, 1, '<', $positive),
array($term1, 'everything', 0, 0, '>', $positive),
array('everything', $term2, 0, 0, '>', $positive),
);
}
/**
* Tests comparing the karma ratings of two terms.
*
* @param string $term1 First term
* @param string $term2 Second term
* @param int $karma1 Karma rating of the first time, 0 or 1
* @param int $karma2 Karma rating of the second term, 0 or 1
* @param string $operator Comparison operator, > or <
* @param string $response Response to check for
*
* @return void
* @dataProvider dataProviderTestKarmaComparisons
*/
public function testKarmaComparisons($term1, $term2, $karma1, $karma2,
$operator, $response
) {
$db = $this->createMockDatabase();
// Reduce answer tables to expected response
$stmt = $db->prepare('DELETE FROM positive_answers WHERE answer != ?');
$stmt->execute(array($response));
$stmt = $db->prepare('DELETE FROM negative_answers WHERE answer != ?');
$stmt->execute(array($response));
if ($karma1) {
$this->checkForKarmaRatingChange($term1, '++', 1);
}
if ($karma2) {
$this->checkForKarmaRatingChange($term2, '++', 1);
}
$args = array(
'receiver' => $this->source,
'text' => $term1 . ' ' . $operator . ' ' . $term2
);
$event = $this->getMockEvent('privmsg', $args);
$this->plugin->setEvent($event);
// Test lack of a response for terms with fixed karma ratings
if ($term1 == 'phergie' || $term2 == 'phergie') {
$callback = 'assertDoesNotEmitEvent';
} else {
$callback = 'assertEmitsEvent';
}
$this->$callback('privmsg', array($event->getSource(), $response));
$this->plugin->onPrivmsg();
// Test for karma changes when one term is "everything"
if ($term1 == 'everything' || $term2 == 'everything') {
if ($term1 == 'everything') {
$term = $term2;
$karma = ($operator == '>') ? -1 : 1;
} else {
$term = $term1;
$karma = ($operator == '>') ? 1 : -1;
}
$event = $this->initiateKarmaEvent($term);
$this->checkForKarmaResponse($event, $term, $term . ' has karma of ' . $karma . '.');
}
}
}

View File

@ -12,15 +12,13 @@
* http://phergie.org/license * http://phergie.org/license
* *
* @category Phergie * @category Phergie
* @package Phergie * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org) * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
require_once(dirname(__FILE__) . '/TestCase.php');
/** /**
* Unit test suite for Pherge_Plugin_Ping. * Unit test suite for Pherge_Plugin_Ping.
* *
@ -28,148 +26,139 @@ require_once(dirname(__FILE__) . '/TestCase.php');
* @package Phergie_Tests * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
class Phergie_Plugin_PingTest extends Phergie_Plugin_TestCase class Phergie_Plugin_PingTest extends Phergie_Plugin_TestCase
{ {
protected $config = array('ping.ping' => 10,
'ping.event' => 300);
/** /**
* Sets up the fixture, for example, opens a network connection. * Tests that the last ping and event are initialized on connection to
* This method is called before a test is executed. * the server.
*/ *
protected function setUp() * @return void
{
$this->setPlugin(new Phergie_Plugin_Ping);
}
/**
* Test the lastEvent setter and getter
*/
public function testSetGetLastEvent()
{
$expected = rand(100000,200000);
$this->plugin->setLastEvent($expected);
$this->assertEquals($expected,
$this->plugin->getLastEvent(),
'Assert that the last event was set and gotten ' .
'correctly');
}
/**
* Test the lastPing setter and getter
*/
public function testSetGetLastPing()
{
$expected = rand(100000,200000);
$this->plugin->setLastPing($expected);
$this->assertEquals($expected,
$this->plugin->getLastPing(),
'Assert that the last ping was set and gotten ' .
'correctly');
}
/**
* Tests the onConnect hook
*/ */
public function testOnConnect() public function testOnConnect()
{ {
$time = time() - 1;
// We need to make sure time() is going to be creater next time it is called
$this->plugin->onConnect(); $this->plugin->onConnect();
$this->assertNull($this->plugin->getLastPing(),
'onConnect should set last ping to null'); $expected = time();
$this->assertGreaterThan($time, $actual = $this->plugin->getLastEvent();
$this->plugin->getLastEvent(), $this->assertEquals($expected, $actual);
'onConnect should update lastEvent with the ' .
'current timestamp'); $expected = null;
$this->assertLessThan($time + 2, $actual = $this->plugin->getLastPing();
$this->plugin->getLastEvent(), $this->assertEquals($expected, $actual);
'onConnect should update lastEvent with the ' .
'current timestamp');
} }
/** /**
* Test that the preEvent method updates the lastEvent with the current time * Tests that the last event is reset when an event occurs.
*
* @return void
*/ */
public function testPreEvent() public function testPreEvent()
{ {
$time = time() -1;
$this->plugin->preEvent(); $this->plugin->preEvent();
$this->assertGreaterThan($time,
$this->plugin->getLastEvent(), $expected = time();
'Last event time was set properly on preEvent'); $actual = $this->plugin->getLastEvent();
$this->assertLessThan($time +2, $this->assertEquals($expected, $actual);
$this->plugin->getLastEvent(),
'Last Event time was set properly on preEvent');
} }
/** /**
* @todo Implement testOnPingResponse(). * Tests that the last ping is reset when a ping is received.
*
* @return void
*/ */
public function testOnPingResponse() public function testOnPingResponse()
{ {
$this->plugin->setLastPing(time());
$this->plugin->onPingResponse(); $this->plugin->onPingResponse();
$this->assertNull($this->plugin->getLastPing(),
'Last ping time should be null after onPingResponse');
$expected = null;
$actual = $this->plugin->getLastPing();
$this->assertEquals($expected, $actual);
} }
/** /**
* Test that the plugin issues a quit when the ping threashold * Tests that the test suite is able to manipulate the value of the last
* has been exceeded * event.
*
* @return void
*/ */
public function testOnTickExceededPingThresholdQuits() public function testSetLastEvent()
{ {
$this->plugin->setLastPing(1); $expected = time() + 1;
$this->plugin->onTick(); $this->plugin->setLastEvent($expected);
$this->assertHasEvent(Phergie_Event_Command::TYPE_QUIT); $actual = $this->plugin->getLastEvent();
} $this->assertEquals($expected, $actual);
/** $this->plugin->setLastEvent();
* Test that the plugin issues a quit when the ping threashold $expected = time();
* has been exceeded $actual = $this->plugin->getLastEvent();
*/ $this->assertEquals($expected, $actual);
public function testOnTickPingWithinThresholdDoesNotQuits()
{ try {
$this->plugin->setLastPing(time()); $this->plugin->setLastEvent('foo');
$this->plugin->onTick(); $this->fail('Expected exception was not thrown');
$this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_QUIT); } catch (Exception $e) { }
} }
/** /**
* Test that a ping is emitted when the event threashold is exceeded * Tests that the test suite is able to manipulate the value of the last
* ping.
*
* @return void
*/ */
public function testPingEmittedAfterThresholdExceeded() public function testSetLastPing()
{ {
$this->plugin->setLastEvent(time() - $this->config['ping.event'] - 1); $expected = time() + 1;
$this->plugin->onTick(); $this->plugin->setLastPing($expected);
$this->assertHasEvent(Phergie_Event_Command::TYPE_PING); $actual = $this->plugin->getLastPing();
$events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PING); $this->assertEquals($expected, $actual);
foreach ($events as $event) {
$this->assertEventEmitter($event, $this->plugin->setLastPing();
$this->plugin, $expected = time();
'Assert that the event was emitted by the tested plugin'); $actual = $this->plugin->getLastPing();
} $this->assertEquals($expected, $actual);
try {
$this->plugin->setLastPing('foo');
$this->fail('Expected exception was not thrown');
} catch (Exception $e) { }
} }
/** /**
* Test that no ping is emitted when the event thresthold is not exceeded * Tests that a ping event is sent after the appropriate time period has
* lapsed since receiving an event.
*
* @depends testSetLastEvent
* @return void
*/ */
public function testNoPingEmittedWhenThresholdNotExceeded() public function testPing()
{ {
$this->plugin->setLastEvent(time() - $this->config['ping.event'] +1); $pingEvent = 10;
$this->setConfig('ping.event', $pingEvent);
$lastEvent = time() - ($pingEvent + 1);
$this->plugin->setLastEvent($lastEvent);
$expected = time();
$this->assertEmitsEvent('ping', array($this->nick, $expected));
$this->plugin->onTick(); $this->plugin->onTick();
$this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_PING); $actual = $this->plugin->getLastPing();
$this->assertEquals($expected, $actual);
} }
public function tearDown() /**
* Tests that a quit event is sent after the appropriate time period has
* lapsed since sending a ping event.
*
* @depends testPing
* @return void
*/
public function testQuit()
{ {
$this->handler->clearEvents(); $pingPing = 10;
$this->setConfig('ping.ping', $pingPing);
$lastPing = time() - ($pingPing + 1);
$this->plugin->setLastPing($lastPing);
$this->assertEmitsEvent('quit');
$this->plugin->onTick();
} }
}
}

View File

@ -12,15 +12,13 @@
* http://phergie.org/license * http://phergie.org/license
* *
* @category Phergie * @category Phergie
* @package Phergie * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org) * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
require_once(dirname(__FILE__) . '/TestCase.php');
/** /**
* Unit test suite for Pherge_Plugin_Pong. * Unit test suite for Pherge_Plugin_Pong.
* *
@ -28,47 +26,21 @@ require_once(dirname(__FILE__) . '/TestCase.php');
* @package Phergie_Tests * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
class Phergie_Plugin_PongTest extends Phergie_Plugin_TestCase class Phergie_Plugin_PongTest extends Phergie_Plugin_TestCase
{ {
/** /**
* Sets up the fixture, for example, opens a network connection. * Test that a pong event is sent when a ping event is received.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->setPlugin(new Phergie_Plugin_Pong);
}
/**
* Test that when a ping is received, a Phergie_Event_Command::TYPE_PONG
* is set to the handler
* *
* @event Phergie_Event_Command::TYPE_PING * @return void
*/ */
public function testOnPing() public function testPong()
{ {
$expected = 'irc.freenode.net';
$event = $this->getMockEvent('ping', array($expected));
$this->plugin->setEvent($event);
$this->assertEmitsEvent('pong', array($expected));
$this->plugin->onPing(); $this->plugin->onPing();
$this->assertHasEvent(Phergie_Event_Command::TYPE_PONG);
} }
/**
* Test that when a ping is received, a Phergie_Event_Command::TYPE_PONG
* is set to the handler
*
* @event Phergie_Event_Command::TYPE_PING
*/
public function testOnPingResponseArguement()
{
$this->plugin->onPing();
$this->assertHasEvent(Phergie_Event_Command::TYPE_PONG);
$events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PONG);
$this->assertTrue(count($events) === 1, 'Assert that only one pong is emitted');
$this->assertEventEmitter(current($events),
$this->plugin,
'Assert that the tested plugin emitted the event');
}
} }

View File

@ -12,15 +12,13 @@
* http://phergie.org/license * http://phergie.org/license
* *
* @category Phergie * @category Phergie
* @package Phergie * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org) * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
require_once dirname(__FILE__) . '/TestCase.php';
/** /**
* Unit test suite for Pherge_Plugin_SpellCheck. * Unit test suite for Pherge_Plugin_SpellCheck.
* *
@ -28,178 +26,141 @@ require_once dirname(__FILE__) . '/TestCase.php';
* @package Phergie_Tests * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
class Phergie_Plugin_SpellCheckTest extends Phergie_Plugin_TestCase class Phergie_Plugin_SpellCheckTest extends Phergie_Plugin_TestCase
{ {
/** /**
* Current SpellCheck plugin instance * Checks for the pspell extension.
* *
* @var Phergie_Plugin_SpellCheck
*/
protected $spell;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*
* @return void * @return void
*/ */
protected function setUp() public function setUp()
{ {
$this->config = array('spellcheck.lang' => 'en'); parent::setUp();
$this->spell = new Phergie_Plugin_SpellCheck(); if (!extension_loaded('pspell')) {
$this->setPlugin(new Phergie_Plugin_Command()); $this->markTestSkipped('pspell extension not available');
$config = $this->plugin->getConfig();
$handler = new Phergie_Plugin_Handler($config, $this->handler);
$this->plugin->setPluginHandler($handler);
$handler->addPlugin($this->plugin);
$handler->addPlugin($this->spell);
$this->spell->setEventHandler($this->handler);
$this->spell->setConnection($this->connection);
}
/**
* @event Phergie_Event_Request::privmsg
* @eventArg #zftalk
* @eventArg spell
*/
public function testSpell()
{
$this->spell->onLoad();
$this->copyEvent();
$this->plugin->onPrivMsg();
$this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_PRIVMSG);
}
/**
* @event Phergie_Event_Request::privmsg
* @eventArg #phergie
* @eventArg spell test
*/
public function testSpellTest()
{
$this->spell->onLoad();
$this->copyEvent();
$this->plugin->onPrivMsg();
$events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PRIVMSG);
$this->assertEquals(1, count($events));
foreach ($events as $event) {
$args = $event->getArguments();
$this->assertEquals('#phergie', $args[0]);
$this->assertContains('CheckSpellUser:', $args[1]);
$this->assertContains('test', $args[1]);
$this->assertContains('correct', $args[1]);
}
}
/**
* @event Phergie_Event_Request::privmsg
* @eventArg #phergie
* @eventArg spell testz
*/
public function testSpellTestz()
{
$this->spell->onLoad();
$this->copyEvent();
$this->plugin->onPrivMsg();
$events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PRIVMSG);
$this->assertEquals(1, count($events));
foreach ($events as $event) {
$args = $event->getArguments();
$this->assertEquals('#phergie', $args[0]);
$this->assertContains('CheckSpellUser:', $args[1]);
$this->assertRegExp('/([a-z]+, ){4}/', $args[1]);
$this->assertContains('testz', $args[1]);
$this->assertContains('test,', $args[1]);
} }
} }
/** /**
* @event Phergie_Event_Request::privmsg * Tests for the plugin failing to load when the language setting is not
* @eventArg #phergie * specified.
* @eventArg spell testz *
*/
public function testSpellMoreSuggestions()
{
$config = $this->spell->getConfig();
$this->copyEvent();
$config['spellcheck.limit'] = 6;
$this->spell->onLoad();
$this->plugin->onPrivMsg();
$events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PRIVMSG);
$this->assertEquals(1, count($events));
foreach ($events as $event) {
$args = $event->getArguments();
$this->assertEquals('#phergie', $args[0]);
$this->assertContains('CheckSpellUser:', $args[1]);
$this->assertRegExp('/([a-z]+, ){5}/', $args[1]);
$this->assertContains('testz', $args[1]);
$this->assertContains('test,', $args[1]);
}
}
/**
* @event Phergie_Event_Request::privmsg
* @eventArg #phergie
* @eventArg spell qwertyuiopasdfghjklzxcvbnm
*/
public function testSpellNoSuggestions()
{
$this->spell->onLoad();
$this->copyEvent();
$this->plugin->onPrivMsg();
$events = $this->getResponseEvents(Phergie_Event_Command::TYPE_PRIVMSG);
$this->assertEquals(1, count($events));
foreach ($events as $event) {
$args = $event->getArguments();
$this->assertEquals('#phergie', $args[0]);
$this->assertContains('CheckSpellUser:', $args[1]);
$this->assertContains('find any suggestions', $args[1]);
}
}
/**
* Copy event from command to spell plugin
*
* @return void * @return void
*/ */
protected function copyEvent() public function testLanguageSettingNotSet()
{ {
$hostmask = Phergie_Hostmask::fromString('CheckSpellUser!test@testing.org'); try {
$this->plugin->onLoad();
$event = $this->plugin->getEvent(); $this->fail('Expected exception was not thrown');
$event->setHostmask($hostmask); } catch (Phergie_Plugin_Exception $e) {
return;
$this->spell->setEvent($event); }
$this->fail('Unexpected exception was thrown');
} }
/**
* Tests for the plugin requiring the Command plugin as a dependency.
*
* @return void
*/
public function testRequiresCommandPlugin()
{
$this->setConfig('spellcheck.lang', 'en');
$this->assertRequiresPlugin('Command');
$this->plugin->onLoad();
}
/**
* Tests for the plugin failing to load because of a dictionary error.
*
* @return void
*/
public function testLoadDictionaryError()
{
$this->setConfig('spellcheck.lang', 'foo');
try {
$this->plugin->onLoad();
$this->fail('Expected exception not thrown');
} catch (Phergie_Plugin_Exception $e) {
return;
}
$this->fail('Unexpected exception was thrown');
}
/**
* Initializes a spell check event.
*
* @param string $word Word to be checked
*
* @return void
*/
private function initializeSpellCheckEvent($word)
{
$this->setConfig('spellcheck.lang', 'en');
$this->plugin->onLoad();
$args = array(
'receiver' => $this->source,
'text' => 'spell ' . $word
);
$event = $this->getMockEvent('privmsg', $args);
$this->plugin->setEvent($event);
}
/**
* Checks for a specified response to a spell check event.
*
* @param string $word Work being checked
* @param string $response Expected response
*
* @return void
*/
private function checkForSpellCheckResponse($word, $response)
{
$this->assertEmitsEvent('privmsg', array($this->source, $response));
$this->plugin->onCommandSpell($word);
}
/**
* Tests for the plugin returning a response for a correctly spelled word.
*
* @return void
*/
public function testRespondsForCorrectlySpelledWord()
{
$word = 'test';
$this->initializeSpellCheckEvent($word);
$response = $this->nick . ': The word "' . $word . '" seems to be spelled correctly.';
$this->checkForSpellCheckResponse($word, $response);
}
/**
* Tests for the plugin returning a response when it can't find any
* suggestions for a word.
*
* @return void
*/
public function testRespondsWithoutSuggestions()
{
$word = 'kjlfljlkjljkljlj';
$this->initializeSpellCheckEvent($word);
$response = $this->nick . ': I could not find any suggestions for "' . $word . '".';
$this->checkForSpellCheckResponse($word, $response);
}
/**
* Tests for the plugin returning a response when it is able to find
* suggestions for a word.
*
* @return void
*/
public function testRespondsWithSuggestions()
{
$word = 'teh';
$this->initializeSpellCheckEvent($word);
$response = $this->nick . ': Suggestions for "' . $word . '": the, Te, tech, Th, eh.';
$this->checkForSpellCheckResponse($word, $response);
}
} }

View File

@ -12,15 +12,13 @@
* http://phergie.org/license * http://phergie.org/license
* *
* @category Phergie * @category Phergie
* @package Phergie * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org) * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
require_once(dirname(__FILE__) . '/TestCase.php');
/** /**
* Unit test suite for Pherge_Plugin_TerryChay. * Unit test suite for Pherge_Plugin_TerryChay.
* *
@ -28,72 +26,110 @@ require_once(dirname(__FILE__) . '/TestCase.php');
* @package Phergie_Tests * @package Phergie_Tests
* @author Phergie Development Team <team@phergie.org> * @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License * @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie * @link http://pear.phergie.org/package/Phergie_Tests
*/ */
class Phergie_Plugin_TerryChayTest extends Phergie_Plugin_TestCase class Phergie_Plugin_TerryChayTest extends Phergie_Plugin_TestCase
{ {
/** /**
* Sets up the fixture, for example, opens a network connection. * Chayism used as a consistent response when related events are
* This method is called before a test is executed. * triggered
*
* @var string
*/ */
protected function setUp() private $chayism = 'Terry Chay doesn\'t need a framework; he already knows everyone\'s code';
/**
* Configures the mock plugin handler to return a mock Http plugin with
* a mock response object populated with predetermined content.
*
* @return void
*/
public function setUpHttpClient()
{ {
$this->setPlugin(new Phergie_Plugin_TerryChay()); $response = $this->getMock('Phergie_Plugin_Http_Response');
$config = new Phergie_Config(); $response
$handler = new Phergie_Plugin_Handler($config, $this->handler); ->expects($this->any())
$this->plugin->setPluginHandler($handler); ->method('getContent')
$handler->addPlugin($this->plugin); ->will($this->returnValue($this->chayism));
$handler->addPlugin(new Phergie_Plugin_Http($config));
$this->plugin->setConfig($config); $plugin = $this->getMock('Phergie_Plugin_Http');
$this->connection->setNick('phergie'); $plugin
->expects($this->any())
->method('get')
->will($this->returnValue($response));
$this->getMockPluginHandler()
->expects($this->any())
->method('getPlugin')
->with('Http')
->will($this->returnValue($plugin));
}
/**
* Tests that the plugin requires the Http plugin as a dependency.
*
* @return void
*/
public function testRequiresHttpPlugin()
{
$this->assertRequiresPlugin('Http');
$this->plugin->onLoad(); $this->plugin->onLoad();
} }
/** /**
* @event Phergie_Event_Request::privmsg * Data provider for testPrivmsgTriggerReturnsChayism().
* @eventArg #zftalk *
* @eventArg tychay * @return array Enumerated array of enumerated arrays each containing
* a set of parameters for a single call to
* testPrivmsgTriggerReturnsChayism()
*/ */
public function testWithTyChay() public function dataProviderTestPrivmsgTriggerReturnsChayism()
{ {
$this->plugin->onPrivMsg(); return array(
$this->assertHasEvent(Phergie_Event_Command::TYPE_PRIVMSG); array('terry chay'),
array('terry chay'),
array('tychay'),
array('!tychay'),
array('! tychay'),
array('foo tychay bar'),
);
} }
/** /**
* @event Phergie_Event_Request::privmsg * Tests that appropriate triggers result in a response with a Chayism.
* @eventArg #zftalk *
* @eventArg terrychay * @return void
* @dataProvider dataProviderTestPrivmsgTriggerReturnsChayism
*/ */
public function testWithTerryChay() public function testPrivmsgTriggerReturnsChayism($trigger)
{ {
$this->plugin->onPrivMsg(); $this->setConfig('command.prefix', '!');
$this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_PRIVMSG, $this->setUpHttpClient();
'string "terrychay" should not invoke a response'); $args = array(
} 'receiver' => $this->source,
'text' => $trigger
/** );
* @event Phergie_Event_Request::privmsg $event = $this->getMockEvent('privmsg', $args);
* @eventArg #zftalk $this->plugin->setEvent($event);
* @eventArg terry chay $this->assertEmitsEvent('privmsg', array($this->source, 'Fact: ' . $this->chayism));
*/ $this->plugin->onPrivmsg();
public function testWithTerry_Chay()
{
$this->plugin->onPrivMsg();
$this->assertHasEvent(Phergie_Event_Command::TYPE_PRIVMSG,
'string "terry chay" should invoke a response');
} }
/** /**
* @event Phergie_Event_Request::privmsg * Tests that lack of an appropriate trigger results in no response with
* @eventArg #zftalk * a Chayism.
* @eventArg Elazar is not Mr. Chay *
* @return void
*/ */
public function testWithNoTyChay() public function testNoPrivmsgTriggerDoesNotReturnChayism()
{ {
$this->plugin->onPrivMsg(); $args = array(
$this->assertDoesNotHaveEvent(Phergie_Event_Command::TYPE_PRIVMSG, 'receiver' => $this->source,
'Failed asserting that elazar is not ' . 'text' => 'foo bar baz'
'tychay'); );
$event = $this->getMockEvent('privmsg', $args);
$this->plugin->setEvent($event);
$this->assertDoesNotEmitEvent('privmsg', array($this->source, 'Fact: ' . $this->chayism));
$this->plugin->onPrivmsg();
} }
} }

View File

@ -20,7 +20,7 @@
*/ */
/** /**
* Unit test suite for Pherge_Plugin classes * Unit test suite for plugin classes.
* *
* @category Phergie * @category Phergie
* @package Phergie_Tests * @package Phergie_Tests
@ -31,177 +31,405 @@
abstract class Phergie_Plugin_TestCase extends PHPUnit_Framework_TestCase abstract class Phergie_Plugin_TestCase extends PHPUnit_Framework_TestCase
{ {
/** /**
* @var Phergie_Event_Handler * Mock configuration
*
* @var Phergie_Config
*/ */
protected $handler; protected $config;
/** /**
* Associative array for configuration setting values, accessed by the
* mock configuration object using a callback
*
* @var array
*/
protected $settings = array();
/**
* Mock connection
*
* @var Phergie_Connection * @var Phergie_Connection
*/ */
protected $connection; protected $connection;
/** /**
* @var array * Mock event handler
*
* @var Phergie_Event_Handler
*/ */
protected $eventArgs; protected $events;
/** /**
* Mock plugin handler
*
* @var Phergie_Plugin_Handler
*/
protected $plugins;
/**
* Plugin instance being tested
*
* @var Phergie_Plugin_Abstract * @var Phergie_Plugin_Abstract
*/ */
protected $plugin; protected $plugin;
/** /**
* @var array * Full name of the plugin class being tested, may be explicitly
*/ * specified in subclasses but is otherwise automatically derived from
protected $config = array(); * the test case class name
/**
* Constructs a test case with the given name.
* *
* @param string $name * @var string
* @param array $data
* @param string $dataName
*/ */
public function __construct($name = NULL, array $data = array(), $dataName = '') protected $pluginClass;
{
parent::__construct($name, $data, $dataName);
$this->connection = new Phergie_Connection();
$this->handler = new Phergie_Event_Handler();
}
/** /**
* Assert that a given event type exists in the event handler * User nick used in any events requiring one
* @param string $event
* @param string $message
*/
public function assertHasEvent($event, $message = null)
{
self::assertTrue($this->handler->hasEventOfType($event), $message);
}
/**
* Assert that a given event type DOES NOT exist in the event handler
* @param string $event
* @param string $message
*/
public function assertDoesNotHaveEvent($event, $message = null)
{
self::assertFalse($this->handler->hasEventOfType($event), $message);
}
/**
* Assert that the emitter of the given command event was the given
* plugin
* *
* @param Phergie_Event_Command $event * @var string
* @param Phergie_Plugin_Abstract $plugin
* @param string $message
*/ */
public function assertEventEmitter(Phergie_Event_Command $event, protected $nick = 'nick';
Phergie_Plugin_Abstract $plugin,
$message = null)
{
$this->assertSame($plugin, $event->getPlugin(), $message);
}
/** /**
* Gets the events added to the handler by the plugin * Event source used in any events requiring one
* @param string $type *
* @return array | null * @var string
*/ */
public function getResponseEvents($type = null) protected $source = '#channel';
/**
* Initializes instance properties.
*
* @return void
*/
public function setUp()
{ {
if (is_string($type) && strlen($type) > 0) { if (empty($this->pluginClass)) {
return $this->handler->getEventsOfType($type); $this->pluginClass = preg_replace('/Test$/', '', get_class($this));
} }
return $this->handler->getEvents();
}
/** if (empty($this->plugin)) {
* Sets the event for the test $this->plugin = new $this->pluginClass;
* @param array $event
* @param array $eventArgs
*/
public function setEvent(array $event, array $eventArgs = null)
{
$eventClass = 'Phergie_Event_Request';
if (is_array($event)) {
$eventClass = $event[0];
$eventType = $event[1];
} else {
throw new InvalidArgumentException("Invalid value for \$event");
} }
$event = new $eventClass();
$event->setType($eventType); $this->plugin->setConfig($this->getMockConfig());
$event->setArguments($eventArgs); $this->plugin->setConnection($this->getMockConnection());
$this->plugin->setEvent($event); $this->plugin->setEventHandler($this->getMockEventHandler());
$this->eventArgs = $eventArgs; $this->plugin->setPluginHandler($this->getMockPluginHandler());
} }
/** /**
* Sets the plugin to be tested * Destroys all initialized instance properties.
* If a plugin requries config for testing, an array placed in *
* $this->config will be parsed into a Phergie_Config object and * @return void
* attached to the plugin
*/ */
protected function setPlugin(Phergie_Plugin_Abstract $plugin) public function tearDown()
{ {
$this->plugin = $plugin; unset(
$this->plugin->setEventHandler($this->handler); $this->plugins,
$this->plugin->setConnection($this->connection); $this->events,
$this->connection->setNick('test'); $this->connection,
if (!empty($this->config)) { $this->config,
$config = new Phergie_Config(); $this->plugin
foreach ($this->config as $configKey => $configValue) { );
$config[$configKey] = $configValue; }
}
$plugin->setConfig($config); /**
* Returns a mock configuration object.
*
* @return Phergie_Config
*/
protected function getMockConfig()
{
if (empty($this->config)) {
$this->config = $this->getMock('Phergie_Config', array('offsetExists', 'offsetGet'));
$this->config
->expects($this->any())
->method('offsetExists')
->will($this->returnCallback(array($this, 'configOffsetExists')));
$this->config
->expects($this->any())
->method('offsetGet')
->will($this->returnCallback(array($this, 'configOffsetGet')));
} }
return $this->config;
} }
/** /**
* Overrides the runTest method to add additional annotations * Returns whether a specific configuration setting has a value. Only
* @return PHPUnit_Framework_TestResult * intended for use by this class, but must be public for PHPUnit to
* call them.
*
* @param string $name Name of the setting
*
* @return boolean TRUE if the setting has a value, FALSE otherwise
*/ */
protected function runTest() public function configOffsetExists($name)
{ {
if (null === $this->plugin) { return isset($this->settings[$name]);
throw new RuntimeException( }
'Tests cannot be run before plugin is set'
/**
* Returns the value of a specific configuration setting. Only intended
* for use by this class, but must be public for PHPUnit to call them.
*
* @param string $name Name of the setting
*
* @return mixed Value of the setting
*/
public function configOffsetGet($name)
{
return $this->settings[$name];
}
/**
* Returns a mock connection object.
*
* @return Phergie_Connection
*/
protected function getMockConnection()
{
if (empty($this->connection)) {
$this->connection = $this->getMock('Phergie_Connection');
$this->connection
->expects($this->any())
->method('getNick')
->will($this->returnValue($this->nick));
}
return $this->connection;
}
/**
* Returns a mock event handler object.
*
* @return Phergie_Event_Handler
*/
protected function getMockEventHandler()
{
if (empty($this->events)) {
$this->events = $this->getMock('Phergie_Event_Handler', array('addEvent'));
}
return $this->events;
}
/**
* Returns a mock plugin handler object.
*
* @return Phergie_Plugin_Handler
*/
protected function getMockPluginHandler()
{
if (empty($this->plugins)) {
$config = $this->getMockConfig();
$events = $this->getMockEventHandler();
$this->plugins = $this->getMock(
'Phergie_Plugin_Handler',
array(), // mock everything
array($config, $events)
); );
} }
return $this->plugins;
// Clean the event handler... important!
$this->handler->clearEvents();
$info = $this->getAnnotations();
$event = null;
$eventArgs = array();
if (isset($info['method']['event']) && isset($info['method']['event'][0])) {
if (!is_string($info['method']['event'][0])) {
throw new InvalidArgumentException(
'Only one event may be specified'
);
}
$event = $info['method']['event'][0];
if (stristr($event, '::')) {
$event = explode('::', $event);
}
}
if (isset($info['method']['eventArg'])) {
$eventArgs = $info['method']['eventArg'];
}
if (null !== $event) {
$this->setEvent($event, $eventArgs);
}
$testResult = parent::runTest();
// Clean the event handler again... just incase this time.
$this->handler->clearEvents();
return $testResult;
} }
/**
* Returns a mock event object.
*
* @param string $type Event type
* @param array $args Optional associative array of event arguments
* @param string $nick Optional user nick to associate with the event
* @param string $source Optional user nick or channel name to associate
* with the event as its source
*
* @return Phergie_Event_Request
*/
protected function getMockEvent($type, array $args = array(),
$nick = null, $source = null
) {
$methods = array('getNick', 'getSource');
foreach (array_keys($args) as $arg) {
if (is_int($arg) || ctype_digit($arg)) {
$methods[] = 'getArgument';
} else {
$methods[] = 'get' . ucfirst($arg);
}
}
$event = $this->getMock(
'Phergie_Event_Request',
$methods
);
$nick = $nick ? $nick : $this->nick;
$event
->expects($this->any())
->method('getNick')
->will($this->returnValue($nick));
$source = $source ? $source : $this->source;
$event
->expects($this->any())
->method('getSource')
->will($this->returnValue($source));
foreach ($args as $key => $value) {
if (is_int($key) || ctype_digit($key)) {
$event
->expects($this->any())
->method('getArgument')
->with($key)
->will($this->returnValue($value));
} else {
$event
->expects($this->any())
->method('get' . ucfirst($key))
->will($this->returnValue($value));
}
}
return $event;
}
/**
* Sets the value of a configuration setting.
*
* @param string $setting Name of the setting
* @param mixed $value Value for the setting
*
* @return void
*/
protected function setConfig($setting, $value)
{
$this->settings[$setting] = $value;
}
/**
* Returns the absolute path to the Phergie/Plugin directory. Useful in
* conjunction with getMockDatabase().
*
* @param string $subpath Optional path to append to the directory path
*
* @return string Directory path
*/
protected function getPluginsPath($subpath = null)
{
$path = realpath(dirname(__FILE__) . '/../../../Phergie/Plugin');
if (!empty($subpath)) {
$path .= '/' . ltrim($subpath, '/');
}
return $path;
}
/**
* Modifies the event handler to include an expectation of an event
* being added by the plugin being tested. Note that this must be called
* BEFORE executing the plugin code intended to initiate the event.
*
* @param string $type Event type
* @param array $args Optional enumerated array of event arguments
*
* @return void
*/
protected function assertEmitsEvent($type, array $args = array())
{
$this->events
->expects($this->at(0))
->method('addEvent')
->with($this->plugin, $type, $args);
}
/**
* Modifies the event handler to include an expectation of an event NOT
* being added by the plugin being tested. Note that this must be called
* BEFORE executing plugin code that may initiate the event.
*
* @param string $type Event type
* @param array $args Optional enumerated array of event arguments
*
* @return void
*/
protected function assertDoesNotEmitEvent($type, array $args = array())
{
// Ugly hack to get around an issue in PHPUnit
// @link http://github.com/sebastianbergmann/phpunit-mock-objects/issues/issue/5#issue/5/comment/343524
$callback = create_function(
'$plugin, $type, $args',
'if (get_class($plugin) == "' . $this->pluginClass . '"
&& $type == "' . $type . '"
&& $args == "' . var_export($args, true) . '") {
trigger_error("Instance of ' . $this->pluginClass
. ' unexpectedly emitted event of type ' . $type
. '", E_USER_ERROR);
}'
);
$this->events
->expects($this->any())
->method('addEvent')
->will($this->returnCallback($callback));
}
/**
* Modifies the plugin handler to include an expectation of a plugin
* being retrieved, indicating a dependency. Note that this must be
* called BEFORE executing the plugin code that may load that plugin
* dependency, which is usually located in onLoad().
*
* @param string $name Short name of the plugin required as a dependency
*
* @return void
*/
public function assertRequiresPlugin($name)
{
$this->plugins
->expects($this->atLeastOnce())
->method('getPlugin')
->with($name);
}
/**
* Creates an in-memory copy of a specified SQLite database file and
* returns a connection to it.
*
* @param string $path Path to the SQLite file to copy
*
* @return PDO Connection to the database copy
*/
public function getMockDatabase($path)
{
$original = new PDO('sqlite:' . $path);
$copy = new PDO('sqlite::memory:');
$result = $original->query('SELECT sql FROM sqlite_master');
while ($sql = $result->fetchColumn()) {
$copy->exec($sql);
}
$tables = array();
$result = $original->query('SELECT name FROM sqlite_master WHERE type = "table"');
while ($table = $result->fetchColumn()) {
$tables[] = $table;
}
foreach ($tables as $table) {
$result = $original->query('SELECT * FROM ' . $table);
$insert = null;
$copy->beginTransaction();
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$columns = array_keys($row);
if (empty($insert)) {
$insert = $copy->prepare(
'INSERT INTO "' . $table . '" (' .
'"' . implode('", "', $columns) . '"' .
') VALUES (' .
':' . implode(', :', $columns) .
')'
);
}
$insert->execute($row);
}
$copy->commit();
unset($insert);
}
return $copy;
}
} }

View File

@ -32,10 +32,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class IrcManager extends ImManager { class IrcManager extends ImManager {
protected $conn = null; protected $conn = null;
protected $lastPing = null; protected $lastPing = null;
protected $messageWaiting = true;
protected $lastMessage = null;
protected $regChecks = array(); protected $regChecks = array();
protected $regChecksLookup = array(); protected $regChecksLookup = array();
protected $connected = false;
/** /**
* Initialize connection to server. * Initialize connection to server.
* *
@ -65,16 +69,55 @@ class IrcManager extends ImManager {
} }
} }
/**
* Request a maximum timeout for listeners before the next idle period.
*
* @return integer Maximum timeout
*/
public function timeout() {
if ($this->messageWaiting) {
return 1;
} else {
return $this->plugin->pinginterval;
}
}
/** /**
* Idle processing for io manager's execution loop. * Idle processing for io manager's execution loop.
* Send keepalive pings to server.
* *
* @return void * @return void
*/ */
public function idle() { public function idle() {
if (empty($this->lastPing) || time() - $this->lastPing > 120) { // Send a ping if necessary
if (empty($this->lastPing) || time() - $this->lastPing > $this->plugin->pinginterval) {
$this->sendPing(); $this->sendPing();
} }
if ($this->connected) {
// Send a waiting message if appropriate
if ($this->messageWaiting && time() - $this->lastMessage > 1) {
$wm = Irc_waiting_message::top();
if ($wm === NULL) {
$this->messageWaiting = false;
return;
}
$data = unserialize($wm->data);
$wm->incAttempts();
if ($this->send_raw_message($data)) {
$wm->delete();
} else {
if ($wm->attempts <= common_config('queue', 'max_retries')) {
// Try again next idle
$wm->releaseClaim();
} else {
// Exceeded the maximum number of retries
$wm->delete();
}
}
}
}
} }
/** /**
@ -90,6 +133,7 @@ class IrcManager extends ImManager {
try { try {
$this->conn->handleEvents(); $this->conn->handleEvents();
} catch (Phergie_Driver_Exception $e) { } catch (Phergie_Driver_Exception $e) {
$this->connected = false;
$this->conn->reconnect(); $this->conn->reconnect();
} }
} }
@ -133,6 +177,7 @@ class IrcManager extends ImManager {
'plugins.autoload' => true, 'plugins.autoload' => true,
// Uncomment to enable debugging output
//'ui.enabled' => true, //'ui.enabled' => true,
'nickserv.password' => $this->plugin->nickservpassword, 'nickserv.password' => $this->plugin->nickservpassword,
@ -142,6 +187,7 @@ class IrcManager extends ImManager {
'statusnet.messagecallback' => array($this, 'handle_irc_message'), 'statusnet.messagecallback' => array($this, 'handle_irc_message'),
'statusnet.regcallback' => array($this, 'handle_reg_response'), 'statusnet.regcallback' => array($this, 'handle_reg_response'),
'statusnet.connectedcallback' => array($this, 'handle_connected'),
'statusnet.unregregexp' => $this->plugin->unregregexp, 'statusnet.unregregexp' => $this->plugin->unregregexp,
'statusnet.regregexp' => $this->plugin->regregexp 'statusnet.regregexp' => $this->plugin->regregexp
) )
@ -150,6 +196,7 @@ class IrcManager extends ImManager {
$this->conn->setConfig($config); $this->conn->setConfig($config);
$this->conn->connect(); $this->conn->connect();
$this->lastPing = time(); $this->lastPing = time();
$this->lastMessage = time();
} }
return $this->conn; return $this->conn;
} }
@ -211,6 +258,39 @@ class IrcManager extends ImManager {
} }
} }
/**
* Called when the connection is established
*
* @return void
*/
public function handle_connected() {
$this->connected = true;
}
/**
* Enters a message into the database for sending when ready
*
* @param string $command Command
* @param array $args Arguments
* @return boolean
*/
protected function enqueue_waiting_message($data) {
$wm = new Irc_waiting_message();
$wm->data = serialize($data);
$wm->prioritise = $data['prioritise'];
$wm->attempts = 0;
$wm->created = common_sql_now();
$result = $wm->insert();
if (!$result) {
common_log_db_error($wm, 'INSERT', __FILE__);
throw new ServerException('DB error inserting IRC waiting queue item');
}
return true;
}
/** /**
* Send a message using the daemon * Send a message using the daemon
* *
@ -223,28 +303,45 @@ class IrcManager extends ImManager {
return false; return false;
} }
if ($data['type'] != 'message') { if ($data['type'] != 'delayedmessage') {
// Nick checking if ($data['type'] != 'message') {
$nickdata = $data['nickdata']; // Nick checking
$usernick = $nickdata['user']->nickname; $nickdata = $data['nickdata'];
$screenname = $nickdata['screenname']; $usernick = $nickdata['user']->nickname;
$screenname = $nickdata['screenname'];
// Cancel any existing checks for this user // Cancel any existing checks for this user
if (isset($this->regChecksLookup[$usernick])) { if (isset($this->regChecksLookup[$usernick])) {
unset($this->regChecks[$this->regChecksLookup[$usernick]]); unset($this->regChecks[$this->regChecksLookup[$usernick]]);
}
$this->regChecks[$screenname] = $nickdata;
$this->regChecksLookup[$usernick] = $screenname;
} }
$this->regChecks[$screenname] = $nickdata; // If there is a backlog or we need to wait, queue the message
$this->regChecksLookup[$usernick] = $screenname; if ($this->messageWaiting || time() - $this->lastMessage < 1) {
$this->enqueue_waiting_message(
array(
'type' => 'delayedmessage',
'prioritise' => $data['prioritise'],
'data' => $data['data']
)
);
$this->messageWaiting = true;
return true;
}
} }
try { try {
$this->conn->send($data['data']['command'], $data['data']['args']); $this->conn->send($data['data']['command'], $data['data']['args']);
} catch (Phergie_Driver_Exception $e) { } catch (Phergie_Driver_Exception $e) {
$this->connected = false;
$this->conn->reconnect(); $this->conn->reconnect();
return false; return false;
} }
$this->lastMessage = time();
return true; return true;
} }