From: Pierre Habouzit (MadCoder Date: Sun, 12 Dec 2004 17:02:34 +0000 (+0000) Subject: oups, tests classes are now in the right place X-Git-Tag: xorg/old~683 X-Git-Url: http://git.polytechnique.org/?a=commitdiff_plain;h=17d9e6f6f115698b3f8ca0494074950e97b1fb82;p=platal.git oups, tests classes are now in the right place git-archimport-id: opensource@polytechnique.org--2005/platal--mainline--0.9--patch-65 --- diff --git a/htdocs/TESTS/__init__.php b/htdocs/TESTS/__init__.php index 0672eb0..01f699f 100644 --- a/htdocs/TESTS/__init__.php +++ b/htdocs/TESTS/__init__.php @@ -1,6 +1,6 @@ _type = $type; + $this->_root = $url->getBasePath(); + $this->_username = false; + $this->_password = false; + } + + /** + * Adds another location to the realm. + * @param SimpleUrl $url Somewhere in realm. + * @access public + */ + function stretch($url) { + $this->_root = $this->_getCommonPath($this->_root, $url->getPath()); + } + + /** + * Finds the common starting path. + * @param string $first Path to compare. + * @param string $second Path to compare. + * @return string Common directories. + * @access private + */ + function _getCommonPath($first, $second) { + $first = explode('/', $first); + $second = explode('/', $second); + for ($i = 0; $i < min(count($first), count($second)); $i++) { + if ($first[$i] != $second[$i]) { + return implode('/', array_slice($first, 0, $i)) . '/'; + } + } + return implode('/', $first) . '/'; + } + + /** + * Sets the identity to try within this realm. + * @param string $username Username in authentication dialog. + * @param string $username Password in authentication dialog. + * @access public + */ + function setIdentity($username, $password) { + $this->_username = $username; + $this->_password = $password; + } + + /** + * Accessor for current identity. + * @return string Last succesful username. + * @access public + */ + function getUsername() { + return $this->_username; + } + + /** + * Accessor for current identity. + * @return string Last succesful password. + * @access public + */ + function getPassword() { + return $this->_password; + } + + /** + * Test to see if the URL is within the directory + * tree of the realm. + * @param SimpleUrl $url URL to test. + * @return boolean True if subpath. + * @access public + */ + function isWithin($url) { + return (strpos($url->getBasePath(), $this->_root) === 0); + } + } + + /** + * Manages security realms. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleAuthenticator { + var $_realms; + + /** + * Starts with no realms set up. + * @access public + */ + function SimpleAuthenticator() { + $this->_realms = array(); + } + + /** + * Adds a new realm centered the current URL. + * Browsers vary wildly on their behaviour in this + * regard. Mozilla ignores the realm and presents + * only when challenged, wasting bandwidth. IE + * just carries on presenting until a new challenge + * occours. SimpleTest tries to follow the spirit of + * the original standards committee and treats the + * base URL as the root of a file tree shaped realm. + * @param SimpleUrl $url Base of realm. + * @param string $type Authentication type for this + * realm. Only Basic authentication + * is currently supported. + * @param string $realm Name of realm. + * @access public + */ + function addRealm($url, $type, $realm) { + $this->_realms[$url->getHost()][$realm] = new SimpleRealm($type, $url); + } + + /** + * Sets the current identity to be presented + * against that realm. + * @param string $host Server hosting realm. + * @param string $realm Name of realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + function setIdentityForRealm($host, $realm, $username, $password) { + if (isset($this->_realms[$host][$realm])) { + $this->_realms[$host][$realm]->setIdentity($username, $password); + } + } + + /** + * Finds the name of the realm by comparing URLs. + * @param SimpleUrl $url URL to test. + * @return SimpleRealm Name of realm. + * @access private + */ + function _findRealmFromUrl($url) { + if (! isset($this->_realms[$url->getHost()])) { + return false; + } + foreach ($this->_realms[$url->getHost()] as $name => $realm) { + if ($realm->isWithin($url)) { + return $realm; + } + } + return false; + } + + /** + * Presents the appropriate headers for this location. + * @param SimpleHttpRequest $request Request to modify. + * @param SimpleUrl $url Base of realm. + * @access public + */ + function addHeaders(&$request, $url) { + if ($url->getUsername() && $url->getPassword()) { + $username = $url->getUsername(); + $password = $url->getPassword(); + } elseif ($realm = $this->_findRealmFromUrl($url)) { + $username = $realm->getUsername(); + $password = $realm->getPassword(); + } else { + return; + } + $this->addBasicHeaders($request, $username, $password); + } + + /** + * Presents the appropriate headers for this + * location for basic authentication. + * @param SimpleHttpRequest $request Request to modify. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + * @static + */ + function addBasicHeaders(&$request, $username, $password) { + if ($username && $password) { + $request->addHeaderLine( + 'Authorization: Basic ' . base64_encode("$username:$password")); + } + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/browser.php b/htdocs/TESTS/simpletest/browser.php new file mode 100644 index 0000000..7715350 --- /dev/null +++ b/htdocs/TESTS/simpletest/browser.php @@ -0,0 +1,966 @@ +_sequence = array(); + $this->_position = -1; + } + + /** + * Test for no entries yet. + * @return boolean True if empty. + * @access private + */ + function _isEmpty() { + return ($this->_position == -1); + } + + /** + * Test for being at the beginning. + * @return boolean True if first. + * @access private + */ + function _atBeginning() { + return ($this->_position == 0) && ! $this->_isEmpty(); + } + + /** + * Test for being at the last entry. + * @return boolean True if last. + * @access private + */ + function _atEnd() { + return ($this->_position + 1 >= count($this->_sequence)) && ! $this->_isEmpty(); + } + + /** + * Adds a successfully fetched page to the history. + * @param string $method GET or POST. + * @param SimpleUrl $url URL of fetch. + * @param array $parameters Any post data with the fetch. + * @access public + */ + function recordEntry($method, $url, $parameters) { + $this->_dropFuture(); + array_push( + $this->_sequence, + array('method' => $method, 'url' => $url, 'parameters' => $parameters)); + $this->_position++; + } + + /** + * Last fetching method for current history + * position. + * @return string GET or POST for this point in + * the history. + * @access public + */ + function getMethod() { + if ($this->_isEmpty()) { + return false; + } + return $this->_sequence[$this->_position]['method']; + } + + /** + * Last fully qualified URL for current history + * position. + * @return SimpleUrl URL for this position. + * @access public + */ + function getUrl() { + if ($this->_isEmpty()) { + return false; + } + return $this->_sequence[$this->_position]['url']; + } + + /** + * Parameters of last fetch from current history + * position. + * @return array Hash of post parameters. + * @access public + */ + function getParameters() { + if ($this->_isEmpty()) { + return false; + } + return $this->_sequence[$this->_position]['parameters']; + } + + /** + * Step back one place in the history. Stops at + * the first page. + * @return boolean True if any previous entries. + * @access public + */ + function back() { + if ($this->_isEmpty() || $this->_atBeginning()) { + return false; + } + $this->_position--; + return true; + } + + /** + * Step forward one place. If already at the + * latest entry then nothing will happen. + * @return boolean True if any future entries. + * @access public + */ + function forward() { + if ($this->_isEmpty() || $this->_atEnd()) { + return false; + } + $this->_position++; + return true; + } + + /** + * Ditches all future entries beyond the current + * point. + * @access private + */ + function _dropFuture() { + if ($this->_isEmpty()) { + return; + } + while (! $this->_atEnd()) { + array_pop($this->_sequence); + } + } + } + + /** + * Simulated web browser. This is an aggregate of + * the user agent, the HTML parsing, request history + * and the last header set. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleBrowser { + var $_user_agent; + var $_page; + var $_history; + var $_ignore_frames; + + /** + * Starts with a fresh browser with no + * cookie or any other state information. The + * exception is that a default proxy will be + * set up if specified in the options. + * @access public + */ + function SimpleBrowser() { + $this->_user_agent = &$this->_createUserAgent(); + $this->_user_agent->useProxy( + SimpleTestOptions::getDefaultProxy(), + SimpleTestOptions::getDefaultProxyUsername(), + SimpleTestOptions::getDefaultProxyPassword()); + $this->_page = &new SimplePage(); + $this->_history = &$this->_createHistory(); + $this->_ignore_frames = false; + } + + /** + * Creates the underlying user agent. + * @return SimpleFetcher Content fetcher. + * @access protected + */ + function &_createUserAgent() { + return new SimpleUserAgent(); + } + + /** + * Creates a new empty history list. + * @return SimpleBrowserHistory New list. + * @access protected + */ + function &_createHistory() { + return new SimpleBrowserHistory(); + } + + /** + * Disables frames support. Frames will not be fetched + * and the frameset page will be used instead. + * @access public + */ + function ignoreFrames() { + $this->_ignore_frames = true; + } + + /** + * Enables frames support. Frames will be fetched from + * now on. + * @access public + */ + function useFrames() { + $this->_ignore_frames = false; + } + + /** + * Parses the raw content into a page. Will load further + * frame pages unless frames are disabled. + * @param SimpleHttpResponse $response Response from fetch. + * @return SimplePage Parsed HTML. + * @access protected + */ + function &_parse($response) { + $builder = &new SimplePageBuilder(); + $page = &$builder->parse($response); + if ($this->_ignore_frames || ! $page->hasFrames()) { + return $page; + } + $frameset = &new SimpleFrameset($page); + foreach ($page->getFrameset() as $key => $url) { + $frame = &$this->_fetch('GET', $url, array()); + $frameset->addFrame($frame, $key); + } + return $frameset; + } + + /** + * Fetches a page. + * @param string $method GET or POST. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash $parameters POST parameters. + * @return SimplePage Parsed page. + * @access private + */ + function &_fetch($method, $url, $parameters) { + $response = &$this->_user_agent->fetchResponse($method, $url, $parameters); + if ($response->isError()) { + return new SimplePage($response); + } + return $this->_parse($response); + } + + /** + * Fetches a page or a single frame if that is the current + * focus. + * @param string $method GET or POST. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash $parameters POST parameters. + * @return string Raw content of page. + * @access private + */ + function _load($method, $url, $parameters = false) { + $frame = $url->getTarget(); + if (! $frame || (strtolower($frame) == '_top')) { + return $this->_loadPage($method, $url, $parameters); + } + return $this->_loadFrame(array($frame), $method, $url, $parameters); + } + + /** + * Fetches a page and makes it the current page/frame. + * @param string $method GET or POST. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash $parameters POST parameters. + * @return string Raw content of page. + * @access private + */ + function _loadPage($method, $url, $parameters = false) { + $this->_page = &$this->_fetch(strtoupper($method), $url, $parameters); + $this->_history->recordEntry( + $this->_page->getMethod(), + $this->_page->getUrl(), + $this->_page->getRequestData()); + return $this->_page->getRaw(); + } + + /** + * Fetches a frame into the existing frameset replacing the + * original. + * @param array $frames List of names to drill down. + * @param string $method GET or POST. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash $parameters POST parameters. + * @return string Raw content of page. + * @access private + */ + function _loadFrame($frames, $method, $url, $parameters = false) { + $page = &$this->_fetch(strtoupper($method), $url, $parameters); + $this->_page->setFrame($frames, $page); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $date Time when session restarted. + * If omitted then all persistent + * cookies are kept. + * @access public + */ + function restartSession($date = false) { + $this->_user_agent->restartSession($date); + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->_user_agent->addHeader($header); + } + + /** + * Ages the cookies by the specified time. + * @param integer $interval Amount in seconds. + * @access public + */ + function ageCookies($interval) { + $this->_user_agent->ageCookies($interval); + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $this->_user_agent->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + return $this->_user_agent->getCookieValue($host, $path, $name); + } + + /** + * Reads the current cookies for the current URL. + * @param string $name Key of cookie to find. + * @return string Null if there is no current URL, false + * if the cookie is not set. + * @access public + */ + function getCurrentCookieValue($name) { + return $this->_user_agent->getBaseCookieValue($name, $this->_page->getUrl()); + } + + /** + * Sets the maximum number of redirects before + * a page will be loaded anyway. + * @param integer $max Most hops allowed. + * @access public + */ + function setMaximumRedirects($max) { + $this->_user_agent->setMaximumRedirects($max); + } + + /** + * Sets the socket timeout for opening a connection. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->_user_agent->setConnectionTimeout($timeout); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username = false, $password = false) { + $this->_user_agent->useProxy($proxy, $username, $password); + } + + /** + * Fetches the page content with a HEAD request. + * Will affect cookies, but will not change the base URL. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash $parameters Additional parameters for GET request. + * @return boolean True if successful. + * @access public + */ + function head($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + $response = &$this->_user_agent->fetchResponse( + 'HEAD', + $url, + $parameters); + return ! $response->isError(); + } + + /** + * Fetches the page content with a simple GET request. + * @param string/SimpleUrl $url Target to fetch. + * @param hash $parameters Additional parameters for GET request. + * @return string Content of page or false. + * @access public + */ + function get($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + return $this->_load('GET', $url, $parameters); + } + + /** + * Fetches the page content with a POST request. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash $parameters POST parameters. + * @return string Content of page. + * @access public + */ + function post($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + return $this->_load('POST', $url, $parameters); + } + + /** + * Equivalent to hitting the retry button on the + * browser. Will attempt to repeat the page fetch. If + * there is no history to repeat it will give false. + * @return string/boolean Content if fetch succeeded + * else false. + * @access public + */ + function retry() { + $frames = $this->_page->getFrameFocus(); + if (count($frames) > 0) { + $this->_loadFrame( + $frames, + $this->_page->getMethod(), + $this->_page->getUrl(), + $this->_page->getRequestData()); + return $this->_page->getRaw(); + } + if ($method = $this->_history->getMethod()) { + $this->_page = &$this->_fetch( + $method, + $this->_history->getUrl(), + $this->_history->getParameters()); + return $this->_page->getRaw(); + } + return false; + } + + /** + * Equivalent to hitting the back button on the + * browser. The browser history is unchanged on + * failure. + * @return boolean True if history entry and + * fetch succeeded + * @access public + */ + function back() { + if (! $this->_history->back()) { + return false; + } + $content = $this->retry(); + if (! $content) { + $this->_history->forward(); + } + return $content; + } + + /** + * Equivalent to hitting the forward button on the + * browser. The browser history is unchanged on + * failure. + * @return boolean True if history entry and + * fetch succeeded + * @access public + */ + function forward() { + if (! $this->_history->forward()) { + return false; + } + $content = $this->retry(); + if (! $content) { + $this->_history->back(); + } + return $content; + } + + /** + * Retries a request after setting the authentication + * for the current realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @return boolean True if successful fetch. Note + * that authentication may still have + * failed. + * @access public + */ + function authenticate($username, $password) { + if (! $this->_page->getRealm()) { + return false; + } + $url = $this->_page->getUrl(); + if (! $url) { + return false; + } + $this->_user_agent->setIdentity( + $url->getHost(), + $this->_page->getRealm(), + $username, + $password); + return $this->retry(); + } + + /** + * Accessor for a breakdown of the frameset. + * @return array Hash tree of frames by name + * or index if no name. + * @access public + */ + function getFrames() { + return $this->_page->getFrames(); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. + * @return integer/string/boolean Label if any, otherwise + * the position in the frameset + * or false if none. + * @access public + */ + function getFrameFocus() { + return $this->_page->getFrameFocus(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + return $this->_page->setFrameFocusByIndex($choice); + } + + /** + * Sets the focus by name. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + return $this->_page->setFrameFocus($name); + } + + /** + * Clears the frame focus. All frames will be searched + * for content. + * @access public + */ + function clearFrameFocus() { + return $this->_page->clearFrameFocus(); + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + return $this->_page->getTransportError(); + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + return $this->_page->getMimeType(); + } + + /** + * Accessor for last response code. + * @return integer Last HTTP response code received. + * @access public + */ + function getResponseCode() { + return $this->_page->getResponseCode(); + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + return $this->_page->getAuthentication(); + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + return $this->_page->getRealm(); + } + + /** + * Accessor for current URL of page or frame if + * focused. + * @return string Location of current page or frame as + * a string. + */ + function getUrl() { + $url = $this->_page->getUrl(); + return $url ? $url->asString() : false; + } + + /** + * Accessor for raw bytes sent down the wire. + * @return string Original text sent. + * @access public + */ + function getRequest() { + return $this->_page->getRequest(); + } + + /** + * Accessor for raw page information. + * @return string Original text content of web page. + * @access public + */ + function getContent() { + return $this->_page->getRaw(); + } + + /** + * Accessor for raw header information. + * @return string Header block. + * @access public + */ + function getHeaders() { + return $this->_page->getHeaders(); + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + return $this->_page->getTitle(); + } + + /** + * Accessor for a list of all fixed links in current page. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getAbsoluteUrls() { + return $this->_page->getAbsoluteUrls(); + } + + /** + * Accessor for a list of all relative links. + * @return array List of urls without hostname. + * @access public + */ + function getRelativeUrls() { + return $this->_page->getRelativeUrls(); + } + + /** + * Sets all form fields with that name. + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setField($name, $value) { + return $this->_page->setField($name, $value); + } + + /** + * Sets all form fields with that name. + * @param string/integer $id Id of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldById($id, $value) { + return $this->_page->setFieldById($id, $value); + } + + /** + * Accessor for a form element value within the page. + * Finds the first match. + * @param string $name Field name. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($name) { + return $this->_page->getField($name); + } + + /** + * Accessor for a form element value within the page. + * @param string/integer $id Id of field in forms. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getFieldById($id) { + return $this->_page->getFieldById($id); + } + + /** + * Clicks the submit button by label. The owning + * form will be submitted by this. + * @param string $label Button label. An unlabeled + * button can be triggered by 'Submit'. + * @return boolean True on success. + * @access public + */ + function clickSubmit($label = 'Submit') { + if (! ($form = &$this->_page->getFormBySubmitLabel($label))) { + return false; + } + return $this->_load( + $form->getMethod(), + $form->getAction(), + $form->submitButtonByLabel($label)); + } + + /** + * Clicks the submit button by name attribute. The owning + * form will be submitted by this. + * @param string $name Button name. + * @return boolean True on success. + * @access public + */ + function clickSubmitByName($name) { + if (! ($form = &$this->_page->getFormBySubmitName($name))) { + return false; + } + return $this->_load( + $form->getMethod(), + $form->getAction(), + $form->submitButtonByName($name)); + } + + /** + * Clicks the submit button by ID attribute of the button + * itself. The owning form will be submitted by this. + * @param string $id Button ID. + * @return boolean True on success. + * @access public + */ + function clickSubmitById($id) { + if (! ($form = &$this->_page->getFormBySubmitId($id))) { + return false; + } + return $this->_load( + $form->getMethod(), + $form->getAction(), + $form->submitButtonById($id)); + } + + /** + * Clicks the submit image by some kind of label. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $label ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @return boolean True on successful submit. + * @access public + */ + function clickImage($label, $x = 1, $y = 1) { + if (! ($form = &$this->_page->getFormByImageLabel($label))) { + return false; + } + return $this->_load( + $form->getMethod(), + $form->getAction(), + $form->submitImageByLabel($label, $x, $y)); + } + + /** + * Clicks the submit image by the name. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $name Name attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @return boolean True on successful submit. + * @access public + */ + function clickImageByName($name, $x = 1, $y = 1) { + if (! ($form = &$this->_page->getFormByImageName($name))) { + return false; + } + return $this->_load( + $form->getMethod(), + $form->getAction(), + $form->submitImageByName($name, $x, $y)); + } + + /** + * Clicks the submit image by ID attribute. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param integer/string $id ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @return boolean True on successful submit. + * @access public + */ + function clickImageById($id, $x = 1, $y = 1) { + if (! ($form = &$this->_page->getFormByImageId($id))) { + return false; + } + return $this->_load( + $form->getMethod(), + $form->getAction(), + $form->submitImageById($id, $x, $y)); + } + + /** + * Submits a form by the ID. + * @param string $id The form ID. No submit button value + * will be sent. + * @return boolean True on success. + * @access public + */ + function submitFormById($id) { + if (! ($form = &$this->_page->getFormById($id))) { + return false; + } + return $this->_load( + $form->getMethod(), + $form->getAction(), + $form->submit()); + } + + /** + * Follows a link by label. Will click the first link + * found with this link text by default, or a later + * one if an index is given. The match ignores case and + * white space issues. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return boolean True if link present. + * @access public + */ + function clickLink($label, $index = 0) { + $urls = $this->_page->getUrlsByLabel($label); + if (count($urls) == 0) { + return false; + } + if (count($urls) < $index + 1) { + return false; + } + $this->_load('GET', $urls[$index]); + return true; + } + + /** + * Tests to see if a link is present by label. + * @param string $label Text of value attribute. + * @return boolean True if link present. + * @access public + */ + function isLink($label) { + return (count($this->_page->getUrlsByLabel($label)) > 0); + } + + /** + * Follows a link by id attribute. + * @param string $id ID attribute value. + * @return boolean True if link present. + * @access public + */ + function clickLinkById($id) { + if (! ($url = $this->_page->getUrlById($id))) { + return false; + } + $this->_load('GET', $url); + return true; + } + + /** + * Tests to see if a link is present by ID attribute. + * @param string $id Text of id attribute. + * @return boolean True if link present. + * @access public + */ + function isLinkById($id) { + return (boolean)$this->_page->getUrlById($id); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/dumper.php b/htdocs/TESTS/simpletest/dumper.php new file mode 100644 index 0000000..5cdb8b9 --- /dev/null +++ b/htdocs/TESTS/simpletest/dumper.php @@ -0,0 +1,363 @@ +getType($value); + switch($type) { + case "Null": + return "NULL"; + case "Boolean": + return "Boolean: " . ($value ? "true" : "false"); + case "Array": + return "Array: " . count($value) . " items"; + case "Object": + return "Object: of " . get_class($value); + case "String": + return "String: " . $this->clipString($value, 100); + default: + return "$type: $value"; + } + return "Unknown"; + } + + /** + * Gets the string representation of a type. + * @param mixed $value Variable to check against. + * @return string Type. + * @access public + */ + function getType($value) { + if (! isset($value)) { + return "Null"; + } elseif (is_bool($value)) { + return "Boolean"; + } elseif (is_string($value)) { + return "String"; + } elseif (is_integer($value)) { + return "Integer"; + } elseif (is_float($value)) { + return "Float"; + } elseif (is_array($value)) { + return "Array"; + } elseif (is_resource($value)) { + return "Resource"; + } elseif (is_object($value)) { + return "Object"; + } + return "Unknown"; + } + + /** + * Creates a human readable description of the + * difference between two variables. Uses a + * dynamic call. + * @param mixed $first First variable. + * @param mixed $second Value to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Description of difference. + * @access public + */ + function describeDifference($first, $second, $identical = false) { + if ($identical) { + if (! $this->_isTypeMatch($first, $second)) { + return "with type mismatch as [" . $this->describeValue($first) . + "] does not match [" . $this->describeValue($second) . "]"; + } + } + $type = $this->getType($first); + if ($type == "Unknown") { + return "with unknown type"; + } + $method = '_describe' . $type . 'Difference'; + return $this->$method($first, $second, $identical); + } + + /** + * Tests to see if types match. + * @param mixed $first First variable. + * @param mixed $second Value to compare with. + * @return boolean True if matches. + * @access private + */ + function _isTypeMatch($first, $second) { + return ($this->getType($first) == $this->getType($second)); + } + + /** + * Clips a string to a maximum length. + * @param string $value String to truncate. + * @param integer $size Minimum string size to show. + * @param integer $position Centre of string section. + * @return string Shortened version. + * @access public + */ + function clipString($value, $size, $position = 0) { + $length = strlen($value); + if ($length <= $size) { + return $value; + } + $position = min($position, $length); + $start = ($size/2 > $position ? 0 : $position - $size/2); + if ($start + $size > $length) { + $start = $length - $size; + } + $value = substr($value, $start, $size); + return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : ""); + } + + /** + * Creates a human readable description of the + * difference between two variables. The minimal + * version. + * @param null $first First value. + * @param mixed $second Value to compare with. + * @return string Human readable description. + * @access private + */ + function _describeGenericDifference($first, $second) { + return "as [" . $this->describeValue($first) . + "] does not match [" . + $this->describeValue($second) . "]"; + } + + /** + * Creates a human readable description of the + * difference between a null and another variable. + * @param null $first First null. + * @param mixed $second Null to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeNullDifference($first, $second, $identical) { + return $this->_describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between a boolean and another variable. + * @param boolean $first First boolean. + * @param mixed $second Boolean to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeBooleanDifference($first, $second, $identical) { + return $this->_describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between a string and another variable. + * @param string $first First string. + * @param mixed $second String to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeStringDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->_describeGenericDifference($first, $second); + } + $position = $this->_stringDiffersAt($first, $second); + $message = "at character $position"; + $message .= " with [" . + $this->clipString($first, 100, $position) . "] and [" . + $this->clipString($second, 100, $position) . "]"; + return $message; + } + + /** + * Creates a human readable description of the + * difference between an integer and another variable. + * @param integer $first First number. + * @param mixed $second Number to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeIntegerDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->_describeGenericDifference($first, $second); + } + return "because [" . $this->describeValue($first) . + "] differs from [" . + $this->describeValue($second) . "] by " . + abs($first - $second); + } + + /** + * Creates a human readable description of the + * difference between two floating point numbers. + * @param float $first First float. + * @param mixed $second Float to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeFloatDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->_describeGenericDifference($first, $second); + } + return "because " . $this->describeValue($first) . + "] differs from [" . + $this->describeValue($second) . "]"; + } + + /** + * Creates a human readable description of the + * difference between two arrays. + * @param array $first First array. + * @param mixed $second Array to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeArrayDifference($first, $second, $identical) { + if (! is_array($second)) { + return $this->_describeGenericDifference($first, $second); + } + if (array_keys($first) !== array_keys($second)) { + return "as key list [" . + implode(", ", array_keys($first)) . "] does not match key list [" . + implode(", ", array_keys($second)) . "]"; + } + foreach (array_keys($first) as $key) { + if ($identical && ($first[$key] === $second[$key])) { + continue; + } + if (! $identical && ($first[$key] == $second[$key])) { + continue; + } + return "with member [$key] " . $this->describeDifference( + $first[$key], + $second[$key], + $identical); + } + return ""; + } + + /** + * Creates a human readable description of the + * difference between a resource and another variable. + * @param resource $first First resource. + * @param mixed $second Resource to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeResourceDifference($first, $second, $identical) { + return $this->_describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between two objects. + * @param object $first First object. + * @param mixed $second Object to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + function _describeObjectDifference($first, $second, $identical) { + if (! is_object($second)) { + return $this->_describeGenericDifference($first, $second); + } + return $this->_describeArrayDifference( + get_object_vars($first), + get_object_vars($second), + $identical); + } + + /** + * Find the first character position that differs + * in two strings by binary chop. + * @param string $first First string. + * @param string $second String to compare with. + * @return integer Position of first differing + * character. + * @access private + */ + function _stringDiffersAt($first, $second) { + if (! $first || ! $second) { + return 0; + } + if (strlen($first) < strlen($second)) { + list($first, $second) = array($second, $first); + } + $position = 0; + $step = strlen($first); + while ($step > 1) { + $step = (integer)(($step + 1)/2); + if (strncmp($first, $second, $position + $step) == 0) { + $position += $step; + } + } + return $position; + } + + /** + * Sends a formatted dump of a variable to a string. + * @param mixed $variable Variable to display. + * @return string Output from print_r(). + * @access public + * @static + */ + function dump($variable) { + ob_start(); + print_r($variable); + $formatted = ob_get_contents(); + ob_end_clean(); + return $formatted; + } + + /** + * Extracts the last assertion that was not within + * Simpletest itself. The name must start with "assert". + * @param array $stack List of stack frames. + * @param string $format String formatting. + * @param string $prefix Prefix of method to search for. + * @access public + * @static + */ + function getFormattedAssertionLine($stack, $format = '%d', $prefix = 'assert') { + foreach ($stack as $frame) { + if (substr(@dirname($frame['file']), -10) == 'simpletest') { + continue; + } + if (strncmp($frame['function'], $prefix, strlen($prefix)) == 0) { + return sprintf($format, $frame['line']); + } + } + return ''; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/errors.php b/htdocs/TESTS/simpletest/errors.php new file mode 100644 index 0000000..52257d1 --- /dev/null +++ b/htdocs/TESTS/simpletest/errors.php @@ -0,0 +1,141 @@ +clear(); + } + + /** + * Adds an error to the front of the queue. + * @param $severity PHP error code. + * @param $message Text of error. + * @param $filename File error occoured in. + * @param $line Line number of error. + * @param $super_globals Hash of PHP super global arrays. + * @access public + */ + function add($severity, $message, $filename, $line, $super_globals) { + array_push( + $this->_queue, + array($severity, $message, $filename, $line, $super_globals)); + } + + /** + * Pulls the earliest error from the queue. + * @return False if none, or a list of error + * information. Elements are: severity + * as the PHP error code, the error message, + * the file with the error, the line number + * and a list of PHP super global arrays. + * @access public + */ + function extract() { + if (count($this->_queue)) { + return array_shift($this->_queue); + } + return false; + } + + /** + * Discards the contents of the error queue. + * @access public + */ + function clear() { + $this->_queue = array(); + } + + /** + * Tests to see if the queue is empty. + * @return True if empty. + */ + function isEmpty() { + return (count($this->_queue) == 0); + } + + /** + * Global access to a single error queue. + * @return Global error queue object. + * @access public + * @static + */ + function &instance() { + static $queue = false; + if (! $queue) { + $queue = new SimpleErrorQueue(); + } + return $queue; + } + + /** + * Converst an error code into it's string + * representation. + * @param $severity PHP integer error code. + * @return String version of error code. + * @access public + * @static + */ + function getSeverityAsString($severity) { + static $map = array( + E_STRICT => 'E_STRICT', + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE'); + return $map[$severity]; + } + } + + /** + * Error handler that simply stashes any errors into the global + * error queue. Simulates the existing behaviour with respect to + * logging errors, but this feature may be removed in future. + * @param $severity PHP error code. + * @param $message Text of error. + * @param $filename File error occoured in. + * @param $line Line number of error. + * @param $super_globals Hash of PHP super global arrays. + * @static + * @access public + */ + function simpleTestErrorHandler($severity, $message, $filename, $line, $super_globals) { + restore_error_handler(); + if (ini_get('log_errors')) { + $label = SimpleErrorQueue::getSeverityAsString($severity); + error_log("$label: $message in $filename on line $line"); + } + if ($severity = $severity & error_reporting()) { + $queue = &SimpleErrorQueue::instance(); + $queue->add($severity, $message, $filename, $line, $super_globals); + } + set_error_handler('simpleTestErrorHandler'); + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/expectation.php b/htdocs/TESTS/simpletest/expectation.php new file mode 100644 index 0000000..9487491 --- /dev/null +++ b/htdocs/TESTS/simpletest/expectation.php @@ -0,0 +1,571 @@ +_dumper = &new SimpleDumper(); + $this->_message = $message; + } + + /** + * Tests the expectation. True if correct. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + * @abstract + */ + function test($compare) { + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + * @abstract + */ + function testMessage($compare) { + } + + /** + * Overlays the generated message onto the stored user + * message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function overlayMessage($compare) { + return sprintf($this->_message, $this->testMessage($compare)); + } + + /** + * Accessor for the dumper. + * @return SimpleDumper Current value dumper. + * @access protected + */ + function &_getDumper() { + return $this->_dumper; + } + } + + /** + * Test for equality. + * @package SimpleTest + * @subpackage UnitTester + */ + class EqualExpectation extends SimpleExpectation { + var $_value; + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function EqualExpectation($value, $message = '%s') { + $this->SimpleExpectation($message); + $this->_value = $value; + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (($this->_value == $compare) && ($compare == $this->_value)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return "Equal expectation [" . $this->_dumper->describeValue($this->_value) . "]"; + } else { + return "Equal expectation fails " . + $this->_dumper->describeDifference($this->_value, $compare); + } + } + + /** + * Accessor for comparison value. + * @return mixed Held value to compare with. + * @access protected + */ + function _getValue() { + return $this->_value; + } + } + + /** + * Test for inequality. + * @package SimpleTest + * @subpackage UnitTester + */ + class NotEqualExpectation extends EqualExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function NotEqualExpectation($value, $message = '%s') { + $this->EqualExpectation($value, $message); + } + + /** + * Tests the expectation. True if it differs from the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + if ($this->test($compare)) { + return "Not equal expectation passes " . + $dumper->describeDifference($this->_getValue(), $compare); + } else { + return "Not equal expectation fails [" . + $dumper->describeValue($this->_getValue()) . + "] matches"; + } + } + } + + /** + * Test for identity. + * @package SimpleTest + * @subpackage UnitTester + */ + class IdenticalExpectation extends EqualExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function IdenticalExpectation($value, $message = '%s') { + $this->EqualExpectation($value, $message); + } + + /** + * Tests the expectation. True if it exactly + * matches the held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return SimpleTestCompatibility::isIdentical($this->_getValue(), $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + if ($this->test($compare)) { + return "Identical expectation [" . $dumper->describeValue($this->_getValue()) . "]"; + } else { + return "Identical expectation [" . $dumper->describeValue($this->_getValue()) . + "] fails with [" . + $this->_dumper->describeValue($compare) . "] " . + $this->_dumper->describeDifference( + $this->_getValue(), + $compare, + TYPE_MATTERS); + } + } + } + + /** + * Test for non-identity. + * @package SimpleTest + * @subpackage UnitTester + */ + class NotIdenticalExpectation extends IdenticalExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function NotIdenticalExpectation($value, $message = '%s') { + $this->IdenticalExpectation($value, $message); + } + + /** + * Tests the expectation. True if it differs from the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + if ($this->test($compare)) { + return "Not identical expectation passes " . + $dumper->describeDifference($this->_getValue(), $compare, TYPE_MATTERS); + } else { + return "Not identical expectation [" . $dumper->describeValue($this->_getValue()) . "] matches"; + } + } + } + + /** + * Test for a pattern using Perl regex rules. + * @package SimpleTest + * @subpackage UnitTester + */ + class WantedPatternExpectation extends SimpleExpectation { + var $_pattern; + + /** + * Sets the value to compare against. + * @param string $pattern Pattern to search for. + * @param string $message Customised message on failure. + * @access public + */ + function WantedPatternExpectation($pattern, $message = '%s') { + $this->SimpleExpectation($message); + $this->_pattern = $pattern; + } + + /** + * Accessor for the pattern. + * @return string Perl regex as string. + * @access protected + */ + function _getPattern() { + return $this->_pattern; + } + + /** + * Tests the expectation. True if the Perl regex + * matches the comparison value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (boolean)preg_match($this->_getPattern(), $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return $this->_decribePatternMatch($this->_getPattern(), $compare); + } else { + $dumper = &$this->_getDumper(); + return "Pattern [" . $this->_getPattern() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } + } + + /** + * Describes a pattern match including the string + * found and it's position. + * @param string $pattern Regex to match against. + * @param string $subject Subject to search. + * @access protected + */ + function _decribePatternMatch($pattern, $subject) { + preg_match($pattern, $subject, $matches); + $position = strpos($subject, $matches[0]); + $dumper = &$this->_getDumper(); + return "Pattern [$pattern] detected at [$position] in [" . + $dumper->describeValue($subject) . "] as [" . + $matches[0] . "] in region [" . + $dumper->clipString($subject, 40, $position) . "]"; + } + } + + /** + * Fail if a pattern is detected within the + * comparison. + * @package SimpleTest + * @subpackage UnitTester + */ + class UnwantedPatternExpectation extends WantedPatternExpectation { + + /** + * Sets the reject pattern + * @param string $pattern Pattern to search for. + * @param string $message Customised message on failure. + * @access public + */ + function UnwantedPatternExpectation($pattern, $message = '%s') { + $this->WantedPatternExpectation($pattern, $message); + } + + /** + * Tests the expectation. False if the Perl regex + * matches the comparison value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param string $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + $dumper = &$this->_getDumper(); + return "Pattern [" . $this->_getPattern() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } else { + return $this->_decribePatternMatch($this->_getPattern(), $compare); + } + } + } + + /** + * Tests either type or class name if it's an object. + * @package SimpleTest + * @subpackage UnitTester + */ + class IsAExpectation extends SimpleExpectation { + var $_type; + + /** + * Sets the type to compare with. + * @param string $type Type or class name. + * @param string $message Customised message on failure. + * @access public + */ + function IsAExpectation($type, $message = '%s') { + $this->SimpleExpectation($message); + $this->_type = $type; + } + + /** + * Accessor for type to check against. + * @return string Type or class name. + * @access protected + */ + function getType() { + return $this->_type; + } + + /** + * Tests the expectation. True if the type or + * class matches the string value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + if (is_object($compare)) { + return SimpleTestCompatibility::isA($compare, $this->_type); + } else { + return (strtolower(gettype($compare)) == $this->_canonicalType($this->_type)); + } + } + + /** + * Coerces type name into a gettype() match. + * @param string $type User type. + * @return string Simpler type. + * @access private + */ + function _canonicalType($type) { + $type = strtolower($type); + $map = array( + 'bool' => 'boolean', + 'float' => 'double', + 'real' => 'double', + 'int' => 'integer'); + if (isset($map[$type])) { + $type = $map[$type]; + } + return $type; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + return "Value [" . $dumper->describeValue($compare) . + "] should be type [" . $this->_type . "]"; + } + } + + /** + * Tests either type or class name if it's an object. + * Will succeed if the type does not match. + * @package SimpleTest + * @subpackage UnitTester + */ + class NotAExpectation extends IsAExpectation { + var $_type; + + /** + * Sets the type to compare with. + * @param string $type Type or class name. + * @param string $message Customised message on failure. + * @access public + */ + function NotAExpectation($type, $message = '%s') { + $this->IsAExpectation($type, $message); + } + + /** + * Tests the expectation. False if the type or + * class matches the string value. + * @param string $compare Comparison value. + * @return boolean True if different. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + return "Value [" . $dumper->describeValue($compare) . + "] should not be type [" . $this->_getType() . "]"; + } + } + + /** + * Tests for existance of a method in an object + * @package SimpleTest + * @subpackage UnitTester + */ + class MethodExistsExpectation extends SimpleExpectation { + var $_method; + + /** + * Sets the value to compare against. + * @param string $method Method to check. + * @param string $message Customised message on failure. + * @access public + * @return void + */ + function MethodExistsExpectation($method, $message = '%s') { + $this->SimpleExpectation($message); + $this->_method = &$method; + } + + /** + * Tests the expectation. True if the method exists in the test object. + * @param string $compare Comparison method name. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (boolean)(is_object($compare) && method_exists($compare, $this->_method)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + if (! is_object($compare)) { + return 'No method on non-object [' . $dumper->describeValue($compare) . ']'; + } + $method = $this->_method; + return "Object [" . $dumper->describeValue($compare) . + "] should contain method [$method]"; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/extensions/pear_test_case.php b/htdocs/TESTS/simpletest/extensions/pear_test_case.php new file mode 100644 index 0000000..fccdfc8 --- /dev/null +++ b/htdocs/TESTS/simpletest/extensions/pear_test_case.php @@ -0,0 +1,183 @@ +SimpleTestCase($label); + $this->_loosely_typed = false; + } + + /** + * Will test straight equality if set to loose + * typing, or identity if not. + * @param $first First value. + * @param $second Comparison value. + * @param $message Message to display. + * @public + */ + function assertEquals($first, $second, $message = "%s", $delta = 0) { + if ($this->_loosely_typed) { + $expectation = &new EqualExpectation($first); + } else { + $expectation = &new IdenticalExpectation($first); + } + $this->assertExpectation($expectation, $second, $message); + } + + /** + * Passes if the value tested is not null. + * @param $value Value to test against. + * @param $message Message to display. + * @public + */ + function assertNotNull($value, $message = "%s") { + parent::assertTrue(isset($value), $message); + } + + /** + * Passes if the value tested is null. + * @param $value Value to test against. + * @param $message Message to display. + * @public + */ + function assertNull($value, $message = "%s") { + parent::assertTrue(!isset($value), $message); + } + + /** + * In PHP5 the identity test tests for the same + * object. THis is a reference test in PHP4. + * @param $first First object handle. + * @param $second Hopefully the same handle. + * @param $message Message to display. + * @public + */ + function assertSame($first, $second, $message = "%s") { + $this->assertExpectation(new IdenticalExpectation($first), $second, $message); + } + + /** + * In PHP5 the identity test tests for the same + * object. THis is a reference test in PHP4. + * @param $first First object handle. + * @param $second Hopefully a different handle. + * @param $message Message to display. + * @public + */ + function assertNotSame($first, $second, $message = "%s") { + $this->assertExpectation(new NotIdenticalExpectation($first), $second, $message); + } + + /** + * Sends pass if the test condition resolves true, + * a fail otherwise. + * @param $condition Condition to test true. + * @param $message Message to display. + * @public + */ + function assertTrue($condition, $message = "%s") { + parent::assertTrue($condition, $message); + } + + /** + * Sends pass if the test condition resolves false, + * a fail otherwise. + * @param $condition Condition to test false. + * @param $message Message to display. + * @public + */ + function assertFalse($condition, $message = "%s") { + parent::assertTrue(!$condition, $message); + } + + /** + * Tests a regex match. Needs refactoring. + * @param $pattern Regex to match. + * @param $subject String to search in. + * @param $message Message to display. + * @public + */ + function assertRegExp($pattern, $subject, $message = "%s") { + $this->assertExpectation( + new WantedPatternExpectation($pattern), + $subject, + $message); + } + + /** + * Tests the type of a value. + * @param $value Value to take type of. + * @param $type Hoped for type. + * @param $message Message to display. + * @public + */ + function assertType($value, $type, $message = "%s") { + parent::assertTrue(gettype($value) == strtolower($type), $message); + } + + /** + * Sets equality operation to act as a simple equal + * comparison only, allowing a broader range of + * matches. + * @param $loosely_typed True for broader comparison. + * @public + */ + function setLooselyTyped($loosely_typed) { + $this->_loosely_typed = $loosely_typed; + } + + /** + * For progress indication during + * a test amongst other things. + * @return Usually one. + * @public + */ + function countTestCases() { + return $this->getSize(); + } + + /** + * Accessor for name, normally just the class + * name. + * @public + */ + function getName() { + return $this->getLabel(); + } + + /** + * Does nothing. For compatibility only. + * @param $name Dummy + * @public + */ + function setName($name) { + } + } +?> diff --git a/htdocs/TESTS/simpletest/extensions/phpunit_test_case.php b/htdocs/TESTS/simpletest/extensions/phpunit_test_case.php new file mode 100644 index 0000000..a0954d8 --- /dev/null +++ b/htdocs/TESTS/simpletest/extensions/phpunit_test_case.php @@ -0,0 +1,108 @@ +SimpleTestCase($label); + } + + /** + * Sends pass if the test condition resolves true, + * a fail otherwise. + * @param $condition Condition to test true. + * @param $message Message to display. + * @public + */ + function assert($condition, $message = false) { + parent::assertTrue($condition, $message); + } + + /** + * Will test straight equality if set to loose + * typing, or identity if not. + * @param $first First value. + * @param $second Comparison value. + * @param $message Message to display. + * @public + */ + function assertEquals($first, $second, $message = false) { + $this->assertExpectation( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will test straight equality if set to loose + * typing, or identity if not. + * @param $first First value. + * @param $second Comparison value. + * @param $message Message to display. + * @public + */ + function assertEqualsMultilineStrings($first, $second, $message = false) { + $this->assertExpectation( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Tests a regex match. + * @param $pattern Regex to match. + * @param $subject String to search in. + * @param $message Message to display. + * @public + */ + function assertRegexp($pattern, $subject, $message = false) { + $this->assertExpectation( + new WantedPatternExpectation($pattern), + $subject, + $message); + } + + /** + * Sends an error which we interpret as a fail + * with a different message for compatibility. + * @param $message Message to display. + * @public + */ + function error($message) { + parent::assertTrue(false, "Error triggered [$message]"); + } + + /** + * Accessor for name. + * @public + */ + function name() { + return $this->getLabel(); + } + } +?> diff --git a/htdocs/TESTS/simpletest/form.php b/htdocs/TESTS/simpletest/form.php new file mode 100644 index 0000000..e16e9e0 --- /dev/null +++ b/htdocs/TESTS/simpletest/form.php @@ -0,0 +1,489 @@ +_method = $tag->getAttribute('method'); + $this->_action = $this->_createAction($tag->getAttribute('action'), $url); + $this->_default_target = false; + $this->_id = $tag->getAttribute('id'); + $this->_buttons = array(); + $this->_images = array(); + $this->_widgets = array(); + } + + /** + * Sets the frame target within a frameset. + * @param string $frame Name of frame. + * @access public + */ + function setDefaultTarget($frame) { + $this->_default_target = $frame; + } + + /** + * Accessor for form action. + * @return string Either get or post. + * @access public + */ + function getMethod() { + return ($this->_method ? strtolower($this->_method) : 'get'); + } + + /** + * Combined action attribute with current location + * to get an absolute form target. + * @param string $action Action attribute from form tag. + * @param SimpleUrl $base Page location. + * @return SimpleUrl Absolute form target. + */ + function _createAction($action, $base) { + if ($action === false) { + return $base; + } + if ($action === true) { + $url = new SimpleUrl(''); + } else { + $url = new SimpleUrl($action); + } + return $url->makeAbsolute($base); + } + + /** + * Absolute URL of the target. + * @return SimpleUrl URL target. + * @access public + */ + function getAction() { + $url = $this->_action; + if ($this->_default_target && ! $url->getTarget()) { + $url->setTarget($this->_default_target); + } + return $url; + } + + /** + * ID field of form for unique identification. + * @return string Unique tag ID. + * @access public + */ + function getId() { + return $this->_id; + } + + /** + * Adds a tag contents to the form. + * @param SimpleWidget $tag Input tag to add. + * @access public + */ + function addWidget($tag) { + if (strtolower($tag->getAttribute('type')) == 'submit') { + $this->_buttons[] = &$tag; + } elseif (strtolower($tag->getAttribute('type')) == 'image') { + $this->_images[] = &$tag; + } else { + if ($tag->getName()) { + $this->_setWidget($tag); + } + } + } + + /** + * Sets the widget into the form, grouping radio + * buttons if any. + * @param SimpleWidget $tag Incoming form control. + * @access private + */ + function _setWidget($tag) { + if (strtolower($tag->getAttribute('type')) == 'radio') { + $this->_addRadioButton($tag); + } elseif (strtolower($tag->getAttribute('type')) == 'checkbox') { + $this->_addCheckbox($tag); + } else { + $this->_widgets[$tag->getName()] = &$tag; + } + } + + /** + * Adds a radio button, building a group if necessary. + * @param SimpleRadioButtonTag $tag Incoming form control. + * @access private + */ + function _addRadioButton($tag) { + if (! isset($this->_widgets[$tag->getName()])) { + $this->_widgets[$tag->getName()] = &new SimpleRadioGroup(); + } + $this->_widgets[$tag->getName()]->addWidget($tag); + } + + /** + * Adds a checkbox, making it a group on a repeated name. + * @param SimpleCheckboxTag $tag Incoming form control. + * @access private + */ + function _addCheckbox($tag) { + if (! isset($this->_widgets[$tag->getName()])) { + $this->_widgets[$tag->getName()] = &$tag; + } elseif (! SimpleTestCompatibility::isA($this->_widgets[$tag->getName()], 'SimpleCheckboxGroup')) { + $previous = &$this->_widgets[$tag->getName()]; + $this->_widgets[$tag->getName()] = &new SimpleCheckboxGroup(); + $this->_widgets[$tag->getName()]->addWidget($previous); + $this->_widgets[$tag->getName()]->addWidget($tag); + } else { + $this->_widgets[$tag->getName()]->addWidget($tag); + } + } + + /** + * Extracts current value from form. + * @param string $name Keyed by widget name. + * @return string Value as string or null + * if not set. + * @access public + */ + function getValue($name) { + if (isset($this->_widgets[$name])) { + return $this->_widgets[$name]->getValue(); + } + foreach ($this->_buttons as $button) { + if ($button->getName() == $name) { + return $button->getValue(); + } + } + return null; + } + + /** + * Extracts current value from form by the ID. + * @param string/integer $id Keyed by widget ID attribute. + * @return string Value as string or null + * if not set. + * @access public + */ + function getValueById($id) { + foreach ($this->_widgets as $widget) { + if ($widget->getAttribute('id') == $id) { + return $widget->getValue(); + } + } + foreach ($this->_buttons as $button) { + if ($button->getAttribute('id') == $id) { + return $button->getValue(); + } + } + return null; + } + + /** + * Sets a widget value within the form. + * @param string $name Name of widget tag. + * @param string $value Value to input into the widget. + * @return boolean True if value is legal, false + * otherwise. If the field is not + * present, nothing will be set. + * @access public + */ + function setField($name, $value) { + if (isset($this->_widgets[$name])) { + return $this->_widgets[$name]->setValue($value); + } + return false; + } + + /** + * Sets a widget value within the form by using the ID. + * @param string/integer $id Name of widget tag. + * @param string $value Value to input into the widget. + * @return boolean True if value is legal, false + * otherwise. If the field is not + * present, nothing will be set. + * @access public + */ + function setFieldById($id, $value) { + foreach (array_keys($this->_widgets) as $name) { + if ($this->_widgets[$name]->getAttribute('id') == $id) { + return $this->setField($name, $value); + } + } + return false; + } + + /** + * Reads the current form values as a hash + * of submitted parameters. Repeated parameters + * appear as a list. + * @return hash Submitted values. + * @access public + */ + function getValues() { + $values = array(); + foreach (array_keys($this->_widgets) as $name) { + $new = $this->_widgets[$name]->getValue(); + if (is_string($new)) { + $values[$name] = $new; + } elseif (is_array($new)) { + $values[$name] = $new; + } + } + return $values; + } + + /** + * Test to see if a form has a submit button with this + * name attribute. + * @param string $name Name to look for. + * @return boolean True if present. + * @access public + */ + function hasSubmitName($name) { + foreach ($this->_buttons as $button) { + if ($button->getName() == $name) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has a submit button with this + * value attribute. + * @param string $label Button label to search for. + * @return boolean True if present. + * @access public + */ + function hasSubmitLabel($label) { + foreach ($this->_buttons as $button) { + if ($button->getLabel() == $label) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has a submit button with this + * ID attribute. + * @param string $id Button ID attribute to search for. + * @return boolean True if present. + * @access public + */ + function hasSubmitId($id) { + foreach ($this->_buttons as $button) { + if ($button->getAttribute('id') == $id) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has a submit button with this + * name attribute. + * @param string $label Button alt attribute to search for + * or nearest equivalent. + * @return boolean True if present. + * @access public + */ + function hasImageLabel($label) { + foreach ($this->_images as $image) { + if ($image->getLabel() == $label) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has a submittable image with this + * field name. + * @param string $name Image name to search for. + * @return boolean True if present. + * @access public + */ + function hasImageName($name) { + foreach ($this->_images as $image) { + if ($image->getName() == $name) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has a submittable image with this + * ID attribute. + * @param string $id Button ID attribute to search for. + * @return boolean True if present. + * @access public + */ + function hasImageId($id) { + foreach ($this->_images as $image) { + if ($image->getAttribute('id') == $id) { + return true; + } + } + return false; + } + + /** + * Gets the submit values for a named button. + * @param string $name Button label to search for. + * @return hash Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitButtonByName($name) { + foreach ($this->_buttons as $button) { + if ($button->getName() == $name) { + return array_merge( + $button->getSubmitValues(), + $this->getValues()); + } + } + return false; + } + + /** + * Gets the submit values for a named button. + * @param string $label Button label to search for. + * @return hash Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitButtonByLabel($label) { + foreach ($this->_buttons as $button) { + if ($button->getLabel() == $label) { + return array_merge( + $button->getSubmitValues(), + $this->getValues()); + } + } + return false; + } + + /** + * Gets the submit values for a button identified by the ID. + * @param string $id Button ID attribute to search for. + * @return hash Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitButtonById($id) { + foreach ($this->_buttons as $button) { + if ($button->getAttribute('id') == $id) { + return array_merge( + $button->getSubmitValues(), + $this->getValues()); + } + } + return false; + } + + /** + * Gets the submit values for an image identified by the alt + * tag or nearest equivalent. + * @param string $label Button label to search for. + * @param integer $x X-coordinate of click. + * @param integer $y Y-coordinate of click. + * @return hash Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitImageByLabel($label, $x, $y) { + foreach ($this->_images as $image) { + if ($image->getAttribute('alt') == $label) { + return array_merge( + $image->getSubmitValues($x, $y), + $this->getValues()); + } + } + return false; + } + + /** + * Gets the submit values for an image identified by the ID. + * @param string $name Image name to search for. + * @param integer $x X-coordinate of click. + * @param integer $y Y-coordinate of click. + * @return hash Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitImageByName($name, $x, $y) { + foreach ($this->_images as $image) { + if ($image->getName() == $name) { + return array_merge( + $image->getSubmitValues($x, $y), + $this->getValues()); + } + } + return false; + } + + /** + * Gets the submit values for an image identified by the ID. + * @param string/integer $id Button ID attribute to search for. + * @param integer $x X-coordinate of click. + * @param integer $y Y-coordinate of click. + * @return hash Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitImageById($id, $x, $y) { + foreach ($this->_images as $image) { + if ($image->getAttribute('id') == $id) { + return array_merge( + $image->getSubmitValues($x, $y), + $this->getValues()); + } + } + return false; + } + + /** + * Simply submits the form without the submit button + * value. Used when there is only one button or it + * is unimportant. + * @return hash Submitted values. + * @access public + */ + function submit() { + return $this->getValues(); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/frames.php b/htdocs/TESTS/simpletest/frames.php new file mode 100644 index 0000000..b2811a0 --- /dev/null +++ b/htdocs/TESTS/simpletest/frames.php @@ -0,0 +1,656 @@ +_frameset = &$page; + $this->_frames = array(); + $this->_focus = false; + $this->_names = array(); + } + + /** + * Adds a parsed page to the frameset. + * @param SimplePage $page Frame page. + * @param string $name Name of frame in frameset. + * @access public + */ + function addFrame(&$page, $name = false) { + $this->_frames[] = &$page; + if ($name) { + $this->_names[$name] = count($this->_frames) - 1; + } + } + + /** + * Replaces existing frame with another. If the + * frame is nested, then the call is passed down + * one level. + * @param array $path Path of frame in frameset. + * @param SimplePage $page Frame source. + * @access public + */ + function setFrame($path, &$page) { + $name = array_shift($path); + if (isset($this->_names[$name])) { + $index = $this->_names[$name]; + } else { + $index = $name - 1; + } + if (count($path) == 0) { + $this->_frames[$index] = &$page; + return; + } + $this->_frames[$index]->setFrame($path, $page); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. Will have the nested + * frame focus if any. + * @return array Labels or indexes of nested frames. + * @access public + */ + function getFrameFocus() { + if ($this->_focus === false) { + return array(); + } + return array_merge( + array($this->_getPublicNameFromIndex($this->_focus)), + $this->_frames[$this->_focus]->getFrameFocus()); + } + + /** + * Turns an internal array index into the frames list + * into a public name, or if none, then a one offset + * index. + * @param integer $subject Internal index. + * @return integer/string Public name. + * @access private + */ + function _getPublicNameFromIndex($subject) { + foreach ($this->_names as $name => $index) { + if ($subject == $index) { + return $name; + } + } + return $subject + 1; + } + + /** + * Sets the focus by index. The integer index starts from 1. + * If already focused and the target frame also has frames, + * then the nested frame will be focused. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + if (is_integer($this->_focus)) { + if ($this->_frames[$this->_focus]->hasFrames()) { + return $this->_frames[$this->_focus]->setFrameFocusByIndex($choice); + } + } + if (($choice < 1) || ($choice > count($this->_frames))) { + return false; + } + $this->_focus = $choice - 1; + return true; + } + + /** + * Sets the focus by name. If already focused and the + * target frame also has frames, then the nested frame + * will be focused. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + if (is_integer($this->_focus)) { + if ($this->_frames[$this->_focus]->hasFrames()) { + return $this->_frames[$this->_focus]->setFrameFocus($name); + } + } + if (in_array($name, array_keys($this->_names))) { + $this->_focus = $this->_names[$name]; + return true; + } + return false; + } + + /** + * Clears the frame focus. + * @access public + */ + function clearFrameFocus() { + $this->_focus = false; + $this->_clearNestedFramesFocus(); + } + + /** + * Clears the frame focus for any nested frames. + * @access private + */ + function _clearNestedFramesFocus() { + for ($i = 0; $i < count($this->_frames); $i++) { + $this->_frames[$i]->clearFrameFocus(); + } + } + + /** + * Test for the presence of a frameset. + * @return boolean Always true. + * @access public + */ + function hasFrames() { + return true; + } + + /** + * Accessor for frames information. + * @return array/string Recursive hash of frame URL strings. + * The key is either a numerical + * index or the name attribute. + * @access public + */ + function getFrames() { + $report = array(); + for ($i = 0; $i < count($this->_frames); $i++) { + $report[$this->_getPublicNameFromIndex($i)] = + $this->_frames[$i]->getFrames(); + } + return $report; + } + + /** + * Accessor for raw text of either all the pages or + * the frame in focus. + * @return string Raw unparsed content. + * @access public + */ + function getRaw() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getRaw(); + } + $raw = ''; + for ($i = 0; $i < count($this->_frames); $i++) { + $raw .= $this->_frames[$i]->getRaw(); + } + return $raw; + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getTransportError(); + } + return $this->_frameset->getTransportError(); + } + + /** + * Request method used to fetch this frame. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getMethod(); + } + return $this->_frameset->getMethod(); + } + + /** + * Original resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + if (is_integer($this->_focus)) { + $url = $this->_frames[$this->_focus]->getUrl(); + $url->setTarget($this->_getPublicNameFromIndex($this->_focus)); + } else { + $url = $this->_frameset->getUrl(); + } + return $url; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getRequestData(); + } + return $this->_frameset->getRequestData(); + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getMimeType(); + } + return $this->_frameset->getMimeType(); + } + + /** + * Accessor for last response code. + * @return integer Last HTTP response code received. + * @access public + */ + function getResponseCode() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getResponseCode(); + } + return $this->_frameset->getResponseCode(); + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getAuthentication(); + } + return $this->_frameset->getAuthentication(); + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getRealm(); + } + return $this->_frameset->getRealm(); + } + + /** + * Accessor for outgoing header information. + * @return string Header block. + * @access public + */ + function getRequest() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getRequest(); + } + return $this->_frameset->getRequest(); + } + + /** + * Accessor for raw header information. + * @return string Header block. + * @access public + */ + function getHeaders() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getHeaders(); + } + return $this->_frameset->getHeaders(); + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + return $this->_frameset->getTitle(); + } + + /** + * Accessor for a list of all fixed links. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getAbsoluteUrls() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getAbsoluteUrls(); + } + $urls = array(); + foreach ($this->_frames as $frame) { + $urls = array_merge($urls, $frame->getAbsoluteUrls()); + } + return array_values(array_unique($urls)); + } + + /** + * Accessor for a list of all relative links. + * @return array List of urls without hostname. + * @access public + */ + function getRelativeUrls() { + if (is_integer($this->_focus)) { + return $this->_frames[$this->_focus]->getRelativeUrls(); + } + $urls = array(); + foreach ($this->_frames as $frame) { + $urls = array_merge($urls, $frame->getRelativeUrls()); + } + return array_values(array_unique($urls)); + } + + /** + * Accessor for URLs by the link label. Label will match + * regardess of whitespace issues and case. + * @param string $label Text of link. + * @return array List of links with that label. + * @access public + */ + function getUrlsByLabel($label) { + if (is_integer($this->_focus)) { + return $this->_tagUrlsWithFrame( + $this->_frames[$this->_focus]->getUrlsByLabel($label), + $this->_focus); + } + $urls = array(); + foreach ($this->_frames as $index => $frame) { + $urls = array_merge( + $urls, + $this->_tagUrlsWithFrame( + $frame->getUrlsByLabel($label), + $index)); + } + return $urls; + } + + /** + * Accessor for a URL by the id attribute. If in a frameset + * then the first link found with that ID attribute is + * returned only. Focus on a frame if you want one from + * a specific part of the frameset. + * @param string $id Id attribute of link. + * @return string URL with that id. + * @access public + */ + function getUrlById($id) { + foreach ($this->_frames as $index => $frame) { + if ($url = $frame->getUrlById($id)) { + if (! $url->gettarget()) { + $url->setTarget($this->_getPublicNameFromIndex($index)); + } + return $url; + } + } + return false; + } + + /** + * Attaches the intended frame index to a list of URLs. + * @param array $urls List of SimpleUrls. + * @param string $frame Name of frame or index. + * @return array List of tagged URLs. + * @access private + */ + function _tagUrlsWithFrame($urls, $frame) { + $tagged = array(); + foreach ($urls as $url) { + if (! $url->getTarget()) { + $url->setTarget($this->_getPublicNameFromIndex($frame)); + } + $tagged[] = $url; + } + return $tagged; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $label Button label, default 'Submit'. + * @return SimpleForm Form object containing the button. + * @access public + */ + function &getFormBySubmitLabel($label) { + return $this->_findForm('getFormBySubmitLabel', $label); + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $name Button name attribute. + * @return SimpleForm Form object containing the button. + * @access public + */ + function &getFormBySubmitName($name) { + return $this->_findForm('getFormBySubmitName', $name); + } + + /** + * Finds a held form by button id. Will only + * search correctly built forms. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $id Button ID attribute. + * @return SimpleForm Form object containing the button. + * @access public + */ + function &getFormBySubmitId($id) { + return $this->_findForm('getFormBySubmitId', $id); + } + + /** + * Finds a held form by image label. Will only + * search correctly built forms. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $label Usually the alt attribute. + * @return SimpleForm Form object containing the image. + * @access public + */ + function &getFormByImageLabel($label) { + return $this->_findForm('getFormByImageLabel', $label); + } + + /** + * Finds a held form by image button id. Will only + * search correctly built forms. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $name Image name. + * @return SimpleForm Form object containing the image. + * @access public + */ + function &getFormByImageName($name) { + return $this->_findForm('getFormByImageName', $name); + } + + /** + * Finds a held form by image button id. Will only + * search correctly built forms. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $id Image ID attribute. + * @return SimpleForm Form object containing the image. + * @access public + */ + function &getFormByImageId($id) { + return $this->_findForm('getFormByImageId', $id); + } + + /** + * Finds a held form by the form ID. A way of + * identifying a specific form when we have control + * of the HTML code. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $id Form label. + * @return SimpleForm Form object containing the matching ID. + * @access public + */ + function &getFormById($id) { + return $this->_findForm('getFormById', $id); + } + + /** + * General form finder. Will search all the frames or + * just the one in focus. + * @param string $method Method to use to find in a page. + * @param string $attribute Label, name or ID. + * @return SimpleForm Form object containing the matching ID. + * @access private + */ + function &_findForm($method, $attribute) { + if (is_integer($this->_focus)) { + return $this->_findFormInFrame( + $this->_frames[$this->_focus], + $this->_focus, + $method, + $attribute); + } + for ($i = 0; $i < count($this->_frames); $i++) { + $form = &$this->_findFormInFrame( + $this->_frames[$i], + $i, + $method, + $attribute); + if ($form) { + return $form; + } + } + return null; + } + + /** + * Finds a form in a page using a form finding method. Will + * also tag the form with the frame name it belongs in. + * @param SimplePage $page Page content of frame. + * @param integer $index Internal frame representation. + * @param string $method Method to use to find in a page. + * @param string $attribute Label, name or ID. + * @return SimpleForm Form object containing the matching ID. + * @access private + */ + function &_findFormInFrame(&$page, $index, $method, $attribute) { + $form = &$this->_frames[$index]->$method($attribute); + if (isset($form)) { + $form->setDefaultTarget($this->_getPublicNameFromIndex($index)); + } + return $form; + } + + /** + * Sets a field on each form in which the field is + * available. + * @param string $name Field name. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setField($name, $value) { + if (is_integer($this->_focus)) { + $this->_frames[$this->_focus]->setField($name, $value); + } else { + for ($i = 0; $i < count($this->_frames); $i++) { + $this->_frames[$i]->setField($name, $value); + } + } + } + + /** + * Sets a field on the form in which the unique field is + * available. + * @param string/integer $id Field ID attribute. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setFieldById($id, $value) { + if (is_integer($this->_focus)) { + $this->_frames[$this->_focus]->setFieldById($id, $value); + } else { + for ($i = 0; $i < count($this->_frames); $i++) { + $this->_frames[$i]->setFieldById($id, $value); + } + } + } + + /** + * Accessor for a form element value within a frameset. + * Finds the first match amongst the frames. + * @param string $name Field name. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($name) { + for ($i = 0; $i < count($this->_frames); $i++) { + $value = $this->_frames[$i]->getField($name); + if (isset($value)) { + return $value; + } + } + return null; + } + + /** + * Accessor for a form element value within a page. + * Finds the first match. + * @param string/integer $id Field ID attribute. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getFieldById($id) { + for ($i = 0; $i < count($this->_frames); $i++) { + $value = $this->_frames[$i]->getFieldById($id); + if (isset($value)) { + return $value; + } + } + return null; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/http.php b/htdocs/TESTS/simpletest/http.php new file mode 100644 index 0000000..bea9658 --- /dev/null +++ b/htdocs/TESTS/simpletest/http.php @@ -0,0 +1,872 @@ +_host = false; + $this->_name = $name; + $this->_value = $value; + $this->_path = ($path ? $this->_fixPath($path) : "/"); + $this->_expiry = false; + if (is_string($expiry)) { + $this->_expiry = strtotime($expiry); + } elseif (is_integer($expiry)) { + $this->_expiry = $expiry; + } + $this->_is_secure = $is_secure; + } + + /** + * Sets the host. The cookie rules determine + * that the first two parts are taken for + * certain TLDs and three for others. If the + * new host does not match these rules then the + * call will fail. + * @param string $host New hostname. + * @return boolean True if hostname is valid. + * @access public + */ + function setHost($host) { + if ($host = $this->_truncateHost($host)) { + $this->_host = $host; + return true; + } + return false; + } + + /** + * Accessor for the truncated host to which this + * cookie applies. + * @return string Truncated hostname. + * @access public + */ + function getHost() { + return $this->_host; + } + + /** + * Test for a cookie being valid for a host name. + * @param string $host Host to test against. + * @return boolean True if the cookie would be valid + * here. + */ + function isValidHost($host) { + return ($this->_truncateHost($host) === $this->getHost()); + } + + /** + * Extracts just the domain part that determines a + * cookie's host validity. + * @param string $host Host name to truncate. + * @return string Domain or false on a bad host. + * @access private + */ + function _truncateHost($host) { + if (preg_match('/[a-z\-]+\.(com|edu|net|org|gov|mil|int)$/i', $host, $matches)) { + return $matches[0]; + } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) { + return $matches[0]; + } + return false; + } + + /** + * Accessor for name. + * @return string Cookie key. + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Accessor for value. A deleted cookie will + * have an empty string for this. + * @return string Cookie value. + * @access public + */ + function getValue() { + return $this->_value; + } + + /** + * Accessor for path. + * @return string Valid cookie path. + * @access public + */ + function getPath() { + return $this->_path; + } + + /** + * Tests a path to see if the cookie applies + * there. The test path must be longer or + * equal to the cookie path. + * @param string $path Path to test against. + * @return boolean True if cookie valid here. + * @access public + */ + function isValidPath($path) { + return (strncmp( + $this->_fixPath($path), + $this->getPath(), + strlen($this->getPath())) == 0); + } + + /** + * Accessor for expiry. + * @return string Expiry string. + * @access public + */ + function getExpiry() { + if (! $this->_expiry) { + return false; + } + return gmdate("D, d M Y H:i:s", $this->_expiry) . " GMT"; + } + + /** + * Test to see if cookie is expired against + * the cookie format time or timestamp. + * Will give true for a session cookie. + * @param integer/string $now Time to test against. Result + * will be false if this time + * is later than the cookie expiry. + * Can be either a timestamp integer + * or a cookie format date. + * @access public + */ + function isExpired($now) { + if (! $this->_expiry) { + return true; + } + if (is_string($now)) { + $now = strtotime($now); + } + return ($this->_expiry < $now); + } + + /** + * Ages the cookie by the specified number of + * seconds. + * @param integer $interval In seconds. + * @public + */ + function agePrematurely($interval) { + if ($this->_expiry) { + $this->_expiry -= $interval; + } + } + + /** + * Accessor for the secure flag. + * @return boolean True if cookie needs SSL. + * @access public + */ + function isSecure() { + return $this->_is_secure; + } + + /** + * Adds a trailing and leading slash to the path + * if missing. + * @param string $path Path to fix. + * @access private + */ + function _fixPath($path) { + if (substr($path, 0, 1) != '/') { + $path = '/' . $path; + } + if (substr($path, -1, 1) != '/') { + $path .= '/'; + } + return $path; + } + } + + /** + * Creates HTTP headers for the end point of + * a HTTP request. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleRoute { + var $_url; + + /** + * Sets the target URL. + * @param SimpleUrl $url URL as object. + * @access public + */ + function SimpleRoute($url) { + $this->_url = $url; + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access protected + */ + function getUrl() { + return $this->_url; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @return string Request line content. + * @access protected + */ + function _getRequestLine($method) { + return $method . ' ' . $this->_url->getPath() . + $this->_url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @return string Host line content. + * @access protected + */ + function _getHostLine() { + $line = 'Host: ' . $this->_url->getHost(); + if ($this->_url->getPort()) { + $line .= ':' . $this->_url->getPort(); + } + return $line; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function &createConnection($method, $timeout) { + $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80; + $socket = &$this->_createSocket( + $this->_url->getScheme() ? $this->_url->getScheme() : 'http', + $this->_url->getHost(), + $this->_url->getPort() ? $this->_url->getPort() : $default_port, + $timeout); + if (! $socket->isError()) { + $socket->write($this->_getRequestLine($method) . "\r\n"); + $socket->write($this->_getHostLine() . "\r\n"); + $socket->write("Connection: close\r\n"); + } + return $socket; + } + + /** + * Factory for socket. + * @param string $scheme Protocol to use. + * @param string $host Hostname to connect to. + * @param integer $port Remote port. + * @param integer $timeout Connection timeout. + * @return SimpleSocket/SimpleSecureSocket New socket. + * @access protected + */ + function &_createSocket($scheme, $host, $port, $timeout) { + if (in_array($scheme, array('https'))) { + return new SimpleSecureSocket($host, $port, $timeout); + } + return new SimpleSocket($host, $port, $timeout); + } + } + + /** + * Creates HTTP headers for the end point of + * a HTTP request via a proxy server. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleProxyRoute extends SimpleRoute { + var $_proxy; + var $_username; + var $_password; + + /** + * Stashes the proxy address. + * @param SimpleUrl $url URL as object. + * @param string $proxy Proxy URL. + * @param string $username Username for autentication. + * @param string $password Password for autentication. + * @access public + */ + function SimpleProxyRoute($url, $proxy, $username = false, $password = false) { + $this->SimpleRoute($url); + $this->_proxy = $proxy; + $this->_username = $username; + $this->_password = $password; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @param SimpleUrl $url URL as object. + * @return string Request line content. + * @access protected + */ + function _getRequestLine($method) { + $url = $this->getUrl(); + $scheme = $url->getScheme() ? $url->getScheme() : 'http'; + $port = $url->getPort() ? ':' . $url->getPort() : ''; + return $method . ' ' . $scheme . '://' . $url->getHost() . $port . + $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @param SimpleUrl $url URL as object. + * @return string Host line content. + * @access protected + */ + function _getHostLine() { + $host = 'Host: ' . $this->_proxy->getHost(); + $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080; + return "$host:$port"; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function &createConnection($method, $timeout) { + $socket = &$this->_createSocket( + $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http', + $this->_proxy->getHost(), + $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080, + $timeout); + if (! $socket->isError()) { + $socket->write($this->_getRequestLine($method) . "\r\n"); + $socket->write($this->_getHostLine() . "\r\n"); + if ($this->_username && $this->_password) { + $socket->write('Proxy-Authorization: Basic ' . + base64_encode($this->_username . ':' . $this->_password) . + "\r\n"); + } + $socket->write("Connection: close\r\n"); + } + return $socket; + } + } + + /** + * HTTP request for a web page. Factory for + * HttpResponse object. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleHttpRequest { + var $_route; + var $_method; + var $_content; + var $_headers; + var $_cookies; + + /** + * Saves the URL ready for fetching. + * @param SimpleRoute $route Request route. + * @param string $method HTTP request method, + * usually GET. + * @param string $content Content to send with request. + * @access public + */ + function SimpleHttpRequest(&$route, $method, $content = '') { + $this->_route = &$route; + $this->_method = $method; + $this->_content = $content; + $this->_headers = array(); + $this->_cookies = array(); + } + + /** + * Fetches the content and parses the headers. + * @param integer $timeout Connection timeout. + * @return SimpleHttpResponse A response which may only have + * an error. + * @access public + */ + function &fetch($timeout) { + $socket = &$this->_route->createConnection($this->_method, $timeout); + if ($socket->isError()) { + return $this->_createResponse($socket); + } + $this->_dispatchRequest($socket, $this->_method, $this->_content); + return $this->_createResponse($socket); + } + + /** + * Sends the headers. + * @param SimpleSocket $socket Open socket. + * @param string $method HTTP request method, + * usually GET. + * @param string $content Content to send with request. + * @access protected + */ + function _dispatchRequest(&$socket, $method, $content) { + if ($content) { + $socket->write("Content-Length: " . strlen($content) . "\r\n"); + $socket->write("Content-Type: application/x-www-form-urlencoded\r\n"); + } + foreach ($this->_headers as $header_line) { + $socket->write($header_line . "\r\n"); + } + if (count($this->_cookies) > 0) { + $socket->write("Cookie: " . $this->_marshallCookies($this->_cookies) . "\r\n"); + } + $socket->write("\r\n"); + if ($content) { + $socket->write($content); + } + } + + /** + * Adds a header line to the request. + * @param string $header_line Text of header line. + * @access public + */ + function addHeaderLine($header_line) { + $this->_headers[] = $header_line; + } + + /** + * Adds a cookie to the request. + * @param SimpleCookie $cookie Additional cookie. + * @access public + */ + function setCookie($cookie) { + $this->_cookies[] = $cookie; + } + + /** + * Serialises the cookie hash ready for + * transmission. + * @param hash $cookies Parsed cookies. + * @return array Cookies in header form. + * @access private + */ + function _marshallCookies($cookies) { + $cookie_pairs = array(); + foreach ($cookies as $cookie) { + $cookie_pairs[] = $cookie->getName() . "=" . $cookie->getValue(); + } + return implode(";", $cookie_pairs); + } + + /** + * Wraps the socket in a response parser. + * @param SimpleSocket $socket Responding socket. + * @return SimpleHttpResponse Parsed response object. + * @access protected + */ + function &_createResponse(&$socket) { + return new SimpleHttpResponse( + $socket, + $this->_method, + $this->_route->getUrl(), + $this->_content); + } + } + + /** + * Request with data to send. Usually PUT or POST. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleHttpPostRequest extends SimpleHttpRequest { + + /** + * Cretaes an HTML form request. + * @param SimpleRoute $route Request target. + * @param array $parameters Content to send. + * @access public + */ + function SimpleHttpPostRequest($route, $parameters) { + $this->SimpleHttpRequest($route, 'POST', $parameters); + } + + /** + * Sends the headers. + * @param SimpleSocket $socket Open socket. + * @param string $method HTTP request method, + * usually GET. + * @param string $content Content to send with request. + * @access protected + */ + function _dispatchRequest(&$socket, $method, $content) { + parent::_dispatchRequest( + $socket, + $method, + SimpleUrl::encodeRequest($content)); + } + } + + /** + * Collection of header lines in the response. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleHttpHeaders { + var $_raw_headers; + var $_response_code; + var $_http_version; + var $_mime_type; + var $_location; + var $_cookies; + var $_authentication; + var $_realm; + + /** + * Parses the incoming header block. + * @param string $headers Header block. + * @access public + */ + function SimpleHttpHeaders($headers) { + $this->_raw_headers = $headers; + $this->_response_code = false; + $this->_http_version = false; + $this->_mime_type = ''; + $this->_location = false; + $this->_cookies = array(); + $this->_authentication = false; + $this->_realm = false; + foreach (split("\r\n", $headers) as $header_line) { + $this->_parseHeaderLine($header_line); + } + } + + /** + * Accessor for parsed HTTP protocol version. + * @return integer HTTP error code. + * @access public + */ + function getHttpVersion() { + return $this->_http_version; + } + + /** + * Accessor for raw header block. + * @return string All headers as raw string. + * @access public + */ + function getRaw() { + return $this->_raw_headers; + } + + /** + * Accessor for parsed HTTP error code. + * @return integer HTTP error code. + * @access public + */ + function getResponseCode() { + return (integer)$this->_response_code; + } + + /** + * Returns the redirected URL or false if + * no redirection. + * @return string URL or false for none. + * @access public + */ + function getLocation() { + return $this->_location; + } + + /** + * Test to see if the response is a valid redirect. + * @return boolean True if valid redirect. + * @access public + */ + function isRedirect() { + return in_array($this->_response_code, array(301, 302, 303, 307)) && + (boolean)$this->getLocation(); + } + + /** + * Test to see if the response is an authentication + * challenge. + * @return boolean True if challenge. + * @access public + */ + function isChallenge() { + return ($this->_response_code == 401) && + (boolean)$this->_authentication && + (boolean)$this->_realm; + } + + /** + * Accessor for MIME type header information. + * @return string MIME type. + * @access public + */ + function getMimeType() { + return $this->_mime_type; + } + + /** + * Accessor for authentication type. + * @return string Type. + * @access public + */ + function getAuthentication() { + return $this->_authentication; + } + + /** + * Accessor for security realm. + * @return string Realm. + * @access public + */ + function getRealm() { + return $this->_realm; + } + + /** + * Accessor for any new cookies. + * @return array List of new cookies. + * @access public + */ + function getNewCookies() { + return $this->_cookies; + } + + /** + * Called on each header line to accumulate the held + * data within the class. + * @param string $header_line One line of header. + * @access protected + */ + function _parseHeaderLine($header_line) { + if (preg_match('/HTTP\/(\d+\.\d+)\s+(.*?)\s/i', $header_line, $matches)) { + $this->_http_version = $matches[1]; + $this->_response_code = $matches[2]; + } + if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) { + $this->_mime_type = trim($matches[1]); + } + if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) { + $this->_location = trim($matches[1]); + } + if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) { + $this->_cookies[] = $this->_parseCookie($matches[1]); + } + if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) { + $this->_authentication = $matches[1]; + $this->_realm = trim($matches[2]); + } + } + + /** + * Parse the Set-cookie content. + * @param string $cookie_line Text after "Set-cookie:" + * @return SimpleCookie New cookie object. + * @access private + */ + function _parseCookie($cookie_line) { + $parts = split(";", $cookie_line); + $cookie = array(); + preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie); + foreach ($parts as $part) { + if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) { + $cookie[$matches[1]] = trim($matches[2]); + } + } + return new SimpleCookie( + $cookie[1], + trim($cookie[2]), + isset($cookie["path"]) ? $cookie["path"] : "", + isset($cookie["expires"]) ? $cookie["expires"] : false); + } + } + + /** + * Basic HTTP response. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleHttpResponse extends StickyError { + var $_method; + var $_url; + var $_request_data; + var $_sent; + var $_content; + var $_headers; + + /** + * Constructor. Reads and parses the incoming + * content and headers. + * @param SimpleSocket $socket Network connection to fetch + * response text from. + * @param string $method HTTP request method. + * @param SimpleUrl $url Resource name. + * @param mixed $request_data Record of content sent. + * @access public + */ + function SimpleHttpResponse(&$socket, $method, $url, $request_data = '') { + $this->StickyError(); + $this->_method = $method; + $this->_url = $url; + $this->_request_data = $request_data; + $this->_sent = $socket->getSent(); + $this->_content = false; + $raw = $this->_readAll($socket); + if ($socket->isError()) { + $this->_setError('Error reading socket [' . $socket->getError() . ']'); + return; + } + $this->_parse($raw); + } + + /** + * Splits up the headers and the rest of the content. + * @param string $raw Content to parse. + * @access private + */ + function _parse($raw) { + if (! $raw) { + $this->_setError('Nothing fetched'); + $this->_headers = &new SimpleHttpHeaders(''); + } elseif (! strstr($raw, "\r\n\r\n")) { + $this->_setError('Could not parse headers'); + $this->_headers = &new SimpleHttpHeaders($raw); + } else { + list($headers, $this->_content) = split("\r\n\r\n", $raw, 2); + $this->_headers = &new SimpleHttpHeaders($headers); + } + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->_method; + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->_url; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->_request_data; + } + + /** + * Raw request that was sent down the wire. + * @return string Bytes actually sent. + * @access public + */ + function getSent() { + return $this->_sent; + } + + /** + * Accessor for the content after the last + * header line. + * @return string All content. + * @access public + */ + function getContent() { + return $this->_content; + } + + /** + * Accessor for header block. The response is the + * combination of this and the content. + * @return SimpleHeaders Wrapped header block. + * @access public + */ + function getHeaders() { + return $this->_headers; + } + + /** + * Accessor for any new cookies. + * @return array List of new cookies. + * @access public + */ + function getNewCookies() { + return $this->_headers->getNewCookies(); + } + + /** + * Reads the whole of the socket output into a + * single string. + * @param SimpleSocket $socket Unread socket. + * @return string Raw output if successful + * else false. + * @access private + */ + function _readAll(&$socket) { + $all = ''; + while (! $this->_isLastPacket($next = $socket->read())) { + $all .= $next; + } + return $all; + } + + /** + * Test to see if the packet from the socket is the + * last one. + * @param string $packet Chunk to interpret. + * @return boolean True if empty or EOF. + * @access private + */ + function _isLastPacket($packet) { + if (is_string($packet)) { + return $packet === ''; + } + return ! $packet; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/mock_objects.php b/htdocs/TESTS/simpletest/mock_objects.php new file mode 100644 index 0000000..83fcef5 --- /dev/null +++ b/htdocs/TESTS/simpletest/mock_objects.php @@ -0,0 +1,1255 @@ +SimpleExpectation(); + } + + /** + * Tests the expectation. Always true. + * @param mixed $compare Ignored. + * @return boolean True. + * @access public + */ + function test($compare) { + return true; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + return 'Wildcard always matches [' . $dumper->describeValue($compare) . ']'; + } + } + + /** + * Parameter comparison assertion. + * @package SimpleTest + * @subpackage MockObjects + */ + class ParametersExpectation extends SimpleExpectation { + var $_expected; + + /** + * Sets the expected parameter list. + * @param array $parameters Array of parameters including + * those that are wildcarded. + * If the value is not an array + * then it is considered to match any. + * @param mixed $wildcard Any parameter matching this + * will always match. + * @param string $message Customised message on failure. + * @access public + */ + function ParametersExpectation($expected = false, $message = '%s') { + $this->SimpleExpectation($message); + $this->_expected = $expected; + } + + /** + * Tests the assertion. True if correct. + * @param array $parameters Comparison values. + * @return boolean True if correct. + * @access public + */ + function test($parameters) { + if (! is_array($this->_expected)) { + return true; + } + if (count($this->_expected) != count($parameters)) { + return false; + } + for ($i = 0; $i < count($this->_expected); $i++) { + if (! $this->_testParameter($parameters[$i], $this->_expected[$i])) { + return false; + } + } + return true; + } + + /** + * Tests an individual parameter. + * @param mixed $parameter Value to test. + * @param mixed $expected Comparison value. + * @return boolean True if expectation + * fulfilled. + * @access private + */ + function _testParameter($parameter, $expected) { + $comparison = $this->_coerceToExpectation($expected); + return $comparison->test($parameter); + } + + /** + * Returns a human readable test message. + * @param array $comparison Incoming parameter list. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($parameters) { + if ($this->test($parameters)) { + return "Expectation of " . count($this->_expected) . + " arguments of [" . $this->_renderArguments($this->_expected) . + "] is correct"; + } else { + return $this->_describeDifference($this->_expected, $parameters); + } + } + + /** + * Message to display if expectation differs from + * the parameters actually received. + * @param array $expected Expected parameters as list. + * @param array $parameters Actual parameters received. + * @return string Description of difference. + * @access private + */ + function _describeDifference($expected, $parameters) { + if (count($expected) != count($parameters)) { + return "Expected " . count($expected) . + " arguments of [" . $this->_renderArguments($expected) . + "] but got " . count($parameters) . + " arguments of [" . $this->_renderArguments($parameters) . "]"; + } + $messages = array(); + for ($i = 0; $i < count($expected); $i++) { + $comparison = $this->_coerceToExpectation($expected[$i]); + if (! $comparison->test($parameters[$i])) { + $messages[] = "parameter " . ($i + 1) . " with [" . + $comparison->overlayMessage($parameters[$i]) . "]"; + } + } + return "Parameter expectation differs at " . implode(" and ", $messages); + } + + /** + * Creates an identical expectation if the + * object/value is not already some type + * of expectation. + * @param mixed $expected Expected value. + * @return SimpleExpectation Expectation object. + * @access private + */ + function _coerceToExpectation($expected) { + if (SimpleTestCompatibility::isA($expected, 'SimpleExpectation')) { + return $expected; + } + return new IdenticalExpectation($expected); + } + + /** + * Renders the argument list as a string for + * messages. + * @param array $args Incoming arguments. + * @return string Simple description of type and value. + * @access private + */ + function _renderArguments($args) { + $descriptions = array(); + if (is_array($args)) { + foreach ($args as $arg) { + $dumper = &new SimpleDumper(); + $descriptions[] = $dumper->describeValue($arg); + } + } + return implode(', ', $descriptions); + } + } + + /** + * Confirms that the number of calls on a method is as expected. + */ + class CallCountExpectation extends SimpleExpectation { + var $_method; + var $_count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Expected number of calls. + * @param string $message Custom error message. + */ + function CallCountExpectation($method, $count, $message = '%s') { + $this->_method = $method; + $this->_count = $count; + $this->SimpleExpectation($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if expected. + * @access public + */ + function test($compare) { + return ($this->_count == $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Expected call count for [' . $this->_method . + '] was [' . $this->_count . + '] got [' . $compare . ']'; + } + } + + /** + * Confirms that the number of calls on a method is as expected. + */ + class MinimumCallCountExpectation extends SimpleExpectation { + var $_method; + var $_count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Minimum number of calls. + * @param string $message Custom error message. + */ + function MinimumCallCountExpectation($method, $count, $message = '%s') { + $this->_method = $method; + $this->_count = $count; + $this->SimpleExpectation($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if enough. + * @access public + */ + function test($compare) { + return ($this->_count <= $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Minimum call count for [' . $this->_method . + '] was [' . $this->_count . + '] got [' . $compare . ']'; + } + } + + /** + * Confirms that the number of calls on a method is as expected. + */ + class MaximumCallCountExpectation extends SimpleExpectation { + var $_method; + var $_count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Minimum number of calls. + * @param string $message Custom error message. + */ + function MaximumCallCountExpectation($method, $count, $message = '%s') { + $this->_method = $method; + $this->_count = $count; + $this->SimpleExpectation($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if not over. + * @access public + */ + function test($compare) { + return ($this->_count >= $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Miximum call count for [' . $this->_method . + '] was [' . $this->_count . + '] got [' . $compare . ']'; + } + } + + /** + * Retrieves values and references by searching the + * parameter lists until a match is found. + * @package SimpleTest + * @subpackage MockObjects + */ + class CallMap { + var $_map; + + /** + * Creates an empty call map. + * @access public + */ + function CallMap() { + $this->_map = array(); + } + + /** + * Stashes a value against a method call. + * @param array $parameters Arguments including wildcards. + * @param mixed $value Value copied into the map. + * @access public + */ + function addValue($parameters, $value) { + $this->addReference($parameters, $value); + } + + /** + * Stashes a reference against a method call. + * @param array $parameters Array of arguments (including wildcards). + * @param mixed $reference Array reference placed in the map. + * @access public + */ + function addReference($parameters, &$reference) { + $place = count($this->_map); + $this->_map[$place] = array(); + $this->_map[$place]["params"] = new ParametersExpectation($parameters); + $this->_map[$place]["content"] = &$reference; + } + + /** + * Searches the call list for a matching parameter + * set. Returned by reference. + * @param array $parameters Parameters to search by + * without wildcards. + * @return object Object held in the first matching + * slot, otherwise null. + * @access public + */ + function &findFirstMatch($parameters) { + $slot = $this->_findFirstSlot($parameters); + if (!isset($slot)) { + return null; + } + return $slot["content"]; + } + + /** + * Searches the call list for a matching parameter + * set. True if successful. + * @param array $parameters Parameters to search by + * without wildcards. + * @return boolean True if a match is present. + * @access public + */ + function isMatch($parameters) { + return ($this->_findFirstSlot($parameters) != null); + } + + /** + * Searches the map for a matching item. + * @param array $parameters Parameters to search by + * without wildcards. + * @return array Reference to slot or null. + * @access private + */ + function &_findFirstSlot($parameters) { + for ($i = 0; $i < count($this->_map); $i++) { + if ($this->_map[$i]["params"]->test($parameters)) { + return $this->_map[$i]; + } + } + return null; + } + } + + /** + * An empty collection of methods that can have their + * return values set. Used for prototyping. + * @package SimpleTest + * @subpackage MockObjects + */ + class SimpleStub { + var $_wildcard; + var $_is_strict; + var $_returns; + var $_return_sequence; + var $_call_counts; + + /** + * Sets up the wildcard and everything else empty. + * @param mixed $wildcard Parameter matching wildcard. + * @param boolean $is_strict Enables method name checks. + * @access public + */ + function SimpleStub($wildcard, $is_strict = true) { + $this->_wildcard = $wildcard; + $this->_is_strict = $is_strict; + $this->_returns = array(); + $this->_return_sequence = array(); + $this->_call_counts = array(); + } + + /** + * Replaces wildcard matches with wildcard + * expectations in the argument list. + * @param array $args Raw argument list. + * @return array Argument list with + * expectations. + * @access private + */ + function _replaceWildcards($args) { + if ($args === false) { + return false; + } + for ($i = 0; $i < count($args); $i++) { + if ($args[$i] === $this->_wildcard) { + $args[$i] = new WildcardExpectation(); + } + } + return $args; + } + + /** + * Returns the expected value for the method name. + * @param string $method Name of method to simulate. + * @param array $args Arguments as an array. + * @return mixed Stored return. + * @access private + */ + function &_invoke($method, $args) { + $method = strtolower($method); + $step = $this->getCallCount($method); + $this->_addCall($method, $args); + return $this->_getReturn($method, $args, $step); + } + + /** + * Triggers a PHP error if the method is not part + * of this object. + * @param string $method Name of method. + * @param string $task Description of task attempt. + * @access protected + */ + function _dieOnNoMethod($method, $task) { + if ($this->_is_strict && !method_exists($this, $method)) { + trigger_error( + "Cannot $task as no ${method}() in class " . get_class($this), + E_USER_ERROR); + } + } + + /** + * Adds one to the call count of a method. + * @param string $method Method called. + * @param array $args Arguments as an array. + * @access protected + */ + function _addCall($method, $args) { + if (!isset($this->_call_counts[$method])) { + $this->_call_counts[$method] = 0; + } + $this->_call_counts[$method]++; + } + + /** + * Fetches the call count of a method so far. + * @param string $method Method name called. + * @return Number of calls so far. + * @access public + */ + function getCallCount($method) { + $this->_dieOnNoMethod($method, "get call count"); + $method = strtolower($method); + if (! isset($this->_call_counts[$method])) { + return 0; + } + return $this->_call_counts[$method]; + } + + /** + * Sets a return for a parameter list that will + * be passed by value for all calls to this method. + * @param string $method Method name. + * @param mixed $value Result of call passed by value. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnValue($method, $value, $args = false) { + $this->_dieOnNoMethod($method, "set return value"); + $args = $this->_replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->_returns[$method])) { + $this->_returns[$method] = new CallMap(); + } + $this->_returns[$method]->addValue($args, $value); + } + + /** + * Sets a return for a parameter list that will + * be passed by value only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $value Result of call passed by value. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnValueAt($timing, $method, $value, $args = false) { + $this->_dieOnNoMethod($method, "set return value sequence"); + $args = $this->_replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->_return_sequence[$method])) { + $this->_return_sequence[$method] = array(); + } + if (! isset($this->_return_sequence[$method][$timing])) { + $this->_return_sequence[$method][$timing] = new CallMap(); + } + $this->_return_sequence[$method][$timing]->addValue($args, $value); + } + + /** + * Sets a return for a parameter list that will + * be passed by reference for all calls. + * @param string $method Method name. + * @param mixed $reference Result of the call will be this object. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnReference($method, &$reference, $args = false) { + $this->_dieOnNoMethod($method, "set return reference"); + $args = $this->_replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->_returns[$method])) { + $this->_returns[$method] = new CallMap(); + } + $this->_returns[$method]->addReference($args, $reference); + } + + /** + * Sets a return for a parameter list that will + * be passed by value only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $reference Result of the call will be this object. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnReferenceAt($timing, $method, &$reference, $args = false) { + $this->_dieOnNoMethod($method, "set return reference sequence"); + $args = $this->_replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->_return_sequence[$method])) { + $this->_return_sequence[$method] = array(); + } + if (! isset($this->_return_sequence[$method][$timing])) { + $this->_return_sequence[$method][$timing] = new CallMap(); + } + $this->_return_sequence[$method][$timing]->addReference($args, $reference); + } + + /** + * Finds the return value matching the incoming + * arguments. If there is no matching value found + * then an error is triggered. + * @param string $method Method name. + * @param array $args Calling arguments. + * @param integer $step Current position in the + * call history. + * @return mixed Stored return. + * @access protected + */ + function &_getReturn($method, $args, $step) { + if (isset($this->_return_sequence[$method][$step])) { + if ($this->_return_sequence[$method][$step]->isMatch($args)) { + return $this->_return_sequence[$method][$step]->findFirstMatch($args); + } + } + if (isset($this->_returns[$method])) { + return $this->_returns[$method]->findFirstMatch($args); + } + return null; + } + } + + /** + * An empty collection of methods that can have their + * return values set and expectations made of the + * calls upon them. The mock will assert the + * expectations against it's attached test case in + * addition to the server stub behaviour. + * @package SimpleTest + * @subpackage MockObjects + */ + class SimpleMock extends SimpleStub { + var $_test; + var $_expected_counts; + var $_max_counts; + var $_expected_args; + var $_expected_args_at; + + /** + * Creates an empty return list and expectation list. + * All call counts are set to zero. + * @param SimpleTestCase $test Test case to test expectations in. + * @param mixed $wildcard Parameter matching wildcard. + * @param boolean $is_strict Enables method name checks on + * expectations. + * @access public + */ + function SimpleMock(&$test, $wildcard, $is_strict = true) { + $this->SimpleStub($wildcard, $is_strict); + if (! $test) { + trigger_error('No unit tester for mock object', E_USER_ERROR); + return; + } + $this->_test = &$test; + $this->_expected_counts = array(); + $this->_max_counts = array(); + $this->_expected_args = array(); + $this->_expected_args_at = array(); + } + + /** + * Accessor for attached unit test so that when + * subclassed, new expectations can be added easily. + * @return SimpleTestCase Unit test passed in constructor. + * @access public + */ + function &getTest() { + return $this->_test; + } + + /** + * Die if bad arguments array is passed + * @param mixed $args The arguments value to be checked. + * @param string $task Description of task attempt. + * @return boolean Valid arguments + * @access private + */ + function _checkArgumentsIsArray($args, $task) { + if (! is_array($args)) { + trigger_error( + "Cannot $task as \$args parameter is not an array", + E_USER_ERROR); + } + } + + /** + * Sets up an expected call with a set of + * expected parameters in that call. All + * calls will be compared to these expectations + * regardless of when the call is made. + * @param string $method Method call to test. + * @param array $args Expected parameters for the call + * including wildcards. + * @param string $message Overridden message. + * @access public + */ + function expectArguments($method, $args, $message = '%s') { + $this->_dieOnNoMethod($method, 'set expected arguments'); + $this->_checkArgumentsIsArray($args, 'set expected arguments'); + $args = $this->_replaceWildcards($args); + $message .= Mock::getExpectationLine(' at line [%d]'); + $this->_expected_args[strtolower($method)] = + new ParametersExpectation($args, $message); + } + + /** + * Sets up an expected call with a set of + * expected parameters in that call. The + * expected call count will be adjusted if it + * is set too low to reach this call. + * @param integer $timing Number of calls in the future at + * which to test. Next call is 0. + * @param string $method Method call to test. + * @param array $args Expected parameters for the call + * including wildcards. + * @param string $message Overridden message. + * @access public + */ + function expectArgumentsAt($timing, $method, $args, $message = '%s') { + $this->_dieOnNoMethod($method, "set expected arguments at time"); + $this->_checkArgumentsIsArray($args, "set expected arguments"); + $args = $this->_replaceWildcards($args); + if (! isset($this->_expected_args_at[$timing])) { + $this->_expected_args_at[$timing] = array(); + } + $method = strtolower($method); + $message .= Mock::getExpectationLine(' at line [%d]'); + $this->_expected_args_at[$timing][$method] = + new ParametersExpectation($args, $message); + } + + /** + * Sets an expectation for the number of times + * a method will be called. The tally method + * is used to check this. + * @param string $method Method call to test. + * @param integer $count Number of times it should + * have been called at tally. + * @param string $message Overridden message. + * @access public + */ + function expectCallCount($method, $count, $message = '%s') { + $this->_dieOnNoMethod($method, "set expected call count"); + $message .= Mock::getExpectationLine(' at line [%d]'); + $this->_expected_counts[strtolower($method)] = + new CallCountExpectation($method, $count, $message); + } + + /** + * Sets the number of times a method may be called + * before a test failure is triggered. + * @param string $method Method call to test. + * @param integer $count Most number of times it should + * have been called. + * @param string $message Overridden message. + * @access public + */ + function expectMaximumCallCount($method, $count, $message = '%s') { + $this->_dieOnNoMethod($method, "set maximum call count"); + $message .= Mock::getExpectationLine(' at line [%d]'); + $this->_max_counts[strtolower($method)] = + new MaximumCallCountExpectation($method, $count, $message); + } + + /** + * Sets the number of times to call a method to prevent + * a failure on the tally. + * @param string $method Method call to test. + * @param integer $count Least number of times it should + * have been called. + * @param string $message Overridden message. + * @access public + */ + function expectMinimumCallCount($method, $count, $message = '%s') { + $this->_dieOnNoMethod($method, "set minimum call count"); + $message .= Mock::getExpectationLine(' at line [%d]'); + $this->_expected_counts[strtolower($method)] = + new MinimumCallCountExpectation($method, $count, $message); + } + + /** + * Convenience method for barring a method + * call. + * @param string $method Method call to ban. + * @param string $message Overridden message. + * @access public + */ + function expectNever($method, $message = '%s') { + $this->expectMaximumCallCount($method, 0, $message); + } + + /** + * Convenience method for a single method + * call. + * @param string $method Method call to track. + * @param array $args Expected argument list or + * false for any arguments. + * @param string $message Overridden message. + * @access public + */ + function expectOnce($method, $args = false, $message = '%s') { + $this->expectCallCount($method, 1, $message); + if ($args !== false) { + $this->expectArguments($method, $args, $message); + } + } + + /** + * Convenience method for requiring a method + * call. + * @param string $method Method call to track. + * @param array $args Expected argument list or + * false for any arguments. + * @param string $message Overridden message. + * @access public + */ + function expectAtLeastOnce($method, $args = false, $message = '%s') { + $this->expectMinimumCallCount($method, 1, $message); + if ($args !== false) { + $this->expectArguments($method, $args, $message); + } + } + + /** + * Totals up the call counts and triggers a test + * assertion if a test is present for expected + * call counts. + * This method must be called explicitly for the call + * count assertions to be triggered. + * @access public + */ + function tally() { + foreach ($this->_expected_counts as $method => $expectation) { + $this->_assertTrue( + $expectation->test($this->getCallCount($method)), + $expectation->overlayMessage($this->getCallCount($method))); + } + } + + /** + * Returns the expected value for the method name + * and checks expectations. Will generate any + * test assertions as a result of expectations + * if there is a test present. + * @param string $method Name of method to simulate. + * @param array $args Arguments as an array. + * @return mixed Stored return. + * @access private + */ + function &_invoke($method, $args) { + $method = strtolower($method); + $step = $this->getCallCount($method); + $this->_addCall($method, $args); + $this->_checkExpectations($method, $args, $step); + return $this->_getReturn($method, $args, $step); + } + + /** + * Tests the arguments against expectations. + * @param string $method Method to check. + * @param array $args Argument list to match. + * @param integer $timing The position of this call + * in the call history. + * @access private + */ + function _checkExpectations($method, $args, $timing) { + if (isset($this->_max_counts[$method])) { + if (! $this->_max_counts[$method]->test($timing + 1)) { + $this->_assertTrue( + false, + $this->_max_counts[$method]->overlayMessage($timing + 1)); + } + } + if (isset($this->_expected_args_at[$timing][$method])) { + $this->_assertTrue( + $this->_expected_args_at[$timing][$method]->test($args), + "Mock method [$method] at [$timing] -> " . + $this->_expected_args_at[$timing][$method]->overlayMessage($args)); + } elseif (isset($this->_expected_args[$method])) { + $this->_assertTrue( + $this->_expected_args[$method]->test($args), + "Mock method [$method] -> " . $this->_expected_args[$method]->overlayMessage($args)); + } + } + + /** + * Triggers an assertion on the held test case. + * Should be overridden when using another test + * framework other than the SimpleTest one if the + * assertion method has a different name. + * @param boolean $assertion True will pass. + * @param string $message Message that will go with + * the test event. + * @access protected + */ + function _assertTrue($assertion, $message) { + $this->_test->assertTrue($assertion, $message); + } + } + + /** + * Static methods only service class for code generation of + * server stubs. + * @package SimpleTest + * @subpackage MockObjects + */ + class Stub { + + /** + * Factory for server stub classes. + */ + function Stub() { + trigger_error('Stub factory methods are class only.'); + } + + /** + * Clones a class' interface and creates a stub version + * that can have return values set. + * @param string $class Class to clone. + * @param string $stub_class New class name. Default is + * the old name with "Stub" + * prepended. + * @param array $methods Additional methods to add beyond + * those in th cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet. + * @static + * @access public + */ + function generate($class, $stub_class = false, $methods = false) { + if (! class_exists($class)) { + return false; + } + if (! $stub_class) { + $stub_class = "Stub" . $class; + } + if (class_exists($stub_class)) { + return false; + } + return eval(Stub::_createClassCode( + $class, + $stub_class, + $methods ? $methods : array()) . " return true;"); + } + + /** + * The new server stub class code in string form. + * @param string $class Class to clone. + * @param string $mock_class New class name. + * @param array $methods Additional methods. + * @static + * @access private + */ + function _createClassCode($class, $stub_class, $methods) { + $stub_base = SimpleTestOptions::getStubBaseClass(); + $code = "class $stub_class extends $stub_base {\n"; + $code .= " function $stub_class(\$wildcard = MOCK_WILDCARD) {\n"; + $code .= " \$this->$stub_base(\$wildcard);\n"; + $code .= " }\n"; + $code .= Stub::_createHandlerCode($class, $stub_base, $methods); + $code .= "}\n"; + return $code; + } + + /** + * Creates code within a class to generate replaced + * methods. All methods call the _invoke() handler + * with the method name and the arguments in an + * array. + * @param string $class Class to clone. + * @param string $base Base class with methods that + * cannot be cloned. + * @param array $methods Additional methods. + * @static + * @access private + */ + function _createHandlerCode($class, $base, $methods) { + $code = ""; + $methods = array_merge($methods, get_class_methods($class)); + foreach ($methods as $method) { + if (($method == '__construct') || ($method == '__clone')) { + continue; + } + if (in_array($method, get_class_methods($base))) { + continue; + } + $code .= " function &$method() {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " return \$this->_invoke(\"$method\", \$args);\n"; + $code .= " }\n"; + } + return $code; + } + } + + /** + * Static methods only service class for code generation of + * mock objects. + * @package SimpleTest + * @subpackage MockObjects + */ + class Mock { + + /** + * Factory for mock object classes. + * @access public + */ + function Mock() { + trigger_error("Mock factory methods are class only."); + } + + /** + * Clones a class' interface and creates a mock version + * that can have return values and expectations set. + * @param string $class Class to clone. + * @param string $mock_class New class name. Default is + * the old name with "Mock" + * prepended. + * @param array $methods Additional methods to add beyond + * those in th cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet. + * @static + * @access public + */ + function generate($class, $mock_class = false, $methods = false) { + if (! class_exists($class)) { + return false; + } + if (! $mock_class) { + $mock_class = "Mock" . $class; + } + if (class_exists($mock_class)) { + return false; + } + return eval(Mock::_createClassCode( + $class, + $mock_class, + $methods ? $methods : array()) . " return true;"); + } + + /** + * Generates a version of a class with selected + * methods mocked only. Inherits the old class + * and chains the mock methods of an aggregated + * mock object. + * @param string $class Class to clone. + * @param string $mock_class New class name. + * @param array $methods Methods to be overridden + * with mock versions. + * @static + * @access public + */ + function generatePartial($class, $mock_class, $methods) { + if (! class_exists($class)) { + return false; + } + if (class_exists($mock_class)) { + trigger_error("Partial mock class [$mock_class] already exists"); + return false; + } + return eval(Mock::_extendClassCode($class, $mock_class, $methods)); + } + + /** + * The new mock class code as a string. + * @param string $class Class to clone. + * @param string $mock_class New class name. + * @param array $methods Additional methods. + * @return string Code for new mock class. + * @static + * @access private + */ + function _createClassCode($class, $mock_class, $methods) { + $mock_base = SimpleTestOptions::getMockBaseClass(); + $code = "class $mock_class extends $mock_base {\n"; + $code .= " function $mock_class(&\$test, \$wildcard = MOCK_WILDCARD) {\n"; + $code .= " \$this->$mock_base(\$test, \$wildcard);\n"; + $code .= " }\n"; + $code .= Stub::_createHandlerCode($class, $mock_base, $methods); + $code .= "}\n"; + return $code; + } + + /** + * The extension class code as a string. The class + * composites a mock object and chains mocked methods + * to it. + * @param string $class Class to extend. + * @param string $mock_class New class name. + * @param array $methods Mocked methods. + * @return string Code for a new class. + * @static + * @access private + */ + function _extendClassCode($class, $mock_class, $methods) { + $mock_base = SimpleTestOptions::getMockBaseClass(); + $code = "class $mock_class extends $class {\n"; + $code .= " var \$_mock;\n"; + $code .= Mock::_addMethodList($methods); + $code .= "\n"; + $code .= " function $mock_class(&\$test, \$wildcard = MOCK_WILDCARD) {\n"; + $code .= " \$this->_mock = &new $mock_base(\$test, \$wildcard, false);\n"; + $code .= " }\n"; + $code .= Mock::_chainMockReturns(); + $code .= Mock::_chainMockExpectations(); + $code .= Mock::_overrideMethods($methods); + $code .= SimpleTestOptions::getPartialMockCode(); + $code .= "}\n"; + return $code; + } + + /** + * Creates a list of mocked methods for error checking. + * @param array $methods Mocked methods. + * @return string Code for a method list. + * @access private + */ + function _addMethodList($methods) { + return " var \$_mocked_methods = array('" . + implode("', '", $methods) . "');\n"; + } + + /** + * Creates code to abandon the expectation if not mocked. + * @param string $alias Parameter name of method name. + * @return string Code for bail out. + * @access private + */ + function _bailOutIfNotMocked($alias) { + $code = " if (! in_array($alias, \$this->_mocked_methods)) {\n"; + $code .= " trigger_error('Method [$alias] is not mocked');\n"; + $code .= " return;\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code for chaining to the composited + * mock object. + * @return string Code for mock set up. + * @access private + */ + function _chainMockReturns() { + $code = " function setReturnValue(\$method, \$value, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->setReturnValue(\$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnReference(\$method, &\$ref, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->setReturnReference(\$method, \$ref, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code for chaining to an aggregated + * mock object. + * @return string Code for expectations. + * @access private + */ + function _chainMockExpectations() { + $code = " function expectArguments(\$method, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectArguments(\$method, \$args);\n"; + $code .= " }\n"; + $code .= " function expectArgumentsAt(\$timing, \$method, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args);\n"; + $code .= " }\n"; + $code .= " function expectCallCount(\$method, \$count) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectCallCount(\$method, \$count);\n"; + $code .= " }\n"; + $code .= " function expectMaximumCallCount(\$method, \$count) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectMaximumCallCount(\$method, \$count);\n"; + $code .= " }\n"; + $code .= " function expectMinimumCallCount(\$method, \$count) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectMinimumCallCount(\$method, \$count);\n"; + $code .= " }\n"; + $code .= " function expectNever(\$method) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectNever(\$method);\n"; + $code .= " }\n"; + $code .= " function expectOnce(\$method, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectOnce(\$method, \$args);\n"; + $code .= " }\n"; + $code .= " function expectAtLeastOnce(\$method, \$args = false) {\n"; + $code .= Mock::_bailOutIfNotMocked("\$method"); + $code .= " \$this->_mock->expectAtLeastOnce(\$method, \$args);\n"; + $code .= " }\n"; + $code .= " function tally() {\n"; + $code .= " \$this->_mock->tally();\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code to override a list of methods + * with mock versions. + * @param array $methods Methods to be overridden + * with mock versions. + * @return string Code for overridden chains. + * @access private + */ + function _overrideMethods($methods) { + $code = ""; + foreach ($methods as $method) { + $code .= " function &$method() {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " return \$this->_mock->_invoke(\"$method\", \$args);\n"; + $code .= " }\n"; + } + return $code; + } + + /** + * Uses a stack trace to find the line of an assertion. + * @param string $format String formatting. + * @param array $stack Stack frames top most first. Only + * needed if not using the PHP + * backtrace function. + * @return string Line number of first assert* + * method embedded in format string. + * @access public + * @static + */ + function getExpectationLine($format = '%d', $stack = false) { + if ($stack === false) { + $stack = SimpleTestCompatibility::getStackTrace(); + } + return SimpleDumper::getFormattedAssertionLine($stack, $format, 'expect'); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/options.php b/htdocs/TESTS/simpletest/options.php new file mode 100644 index 0000000..bb474d3 --- /dev/null +++ b/htdocs/TESTS/simpletest/options.php @@ -0,0 +1,295 @@ + 'SimpleStub', + 'MockBaseClass' => 'SimpleMock', + 'IgnoreList' => array(), + 'AdditionalPartialMockCode' => '', + 'DefaultProxy' => false, + 'DefaultProxyUsername' => false, + 'DefaultProxyPassword' => false); + } + } + + /** + * Static methods for compatibility between different + * PHP versions. + * @package SimpleTest + */ + class SimpleTestCompatibility { + + /** + * Identity test. Drops back to equality for PHP5 + * objects as the === operator counts as the + * stronger reference constraint. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @access public + * @static + */ + function isIdentical($first, $second) { + if (version_compare(phpversion(), '5') >= 0) { + if (gettype($first) != gettype($second)) { + return false; + } + if ($first != $second) { + return false; + } + if (is_object($first) && is_object($second)) { + return (get_class($first) == get_class($second)); + } + if (is_array($first) && is_array($second)) { + if (array_keys($first) != array_keys($second)) { + return false; + } + foreach (array_keys($first) as $key) { + if (! SimpleTestCompatibility::isIdentical($first[$key], $second[$key])) { + return false; + } + } + } + return true; + } + return ($first === $second); + } + + /** + * Test to see if an object is a member of a + * class hiearchy. + * @param object $object Object to test. + * @param string $class Root name of hiearchy. + * @access public + * @static + */ + function isA($object, $class) { + if (version_compare(phpversion(), '5') >= 0) { + if (! class_exists($class)) { + return false; + } + eval("\$is_a = \$object instanceof $class;"); + return $is_a; + } + if (function_exists('is_a')) { + return is_a($object, $class); + } + return ((strtolower($class) == get_class($object)) + or (is_subclass_of($object, $class))); + } + + /** + * Sets a socket timeout for each chunk. + * @param resource $handle Socket handle. + * @param integer $timeout Limit in seconds. + * @access public + * @static + */ + function setTimeout($handle, $timeout) { + stream_set_timeout($handle, $timeout, 0); + } + + /** + * Gets the current stack trace topmost first. + * @return array List of stack frames. + * @access public + * @static + */ + function getStackTrace() { + if (function_exists('debug_backtrace')) { + return array_reverse(debug_backtrace()); + } + return array(); + } + } +?> diff --git a/htdocs/TESTS/simpletest/page.php b/htdocs/TESTS/simpletest/page.php new file mode 100644 index 0000000..c8d2bb7 --- /dev/null +++ b/htdocs/TESTS/simpletest/page.php @@ -0,0 +1,942 @@ +SimpleSaxListener(); + } + + /** + * Reads the raw content and send events + * into the page to be built. + * @param $response SimpleHttpResponse Fetched response. + * @return SimplePage Newly parsed page. + * @access public + */ + function parse($response) { + $this->_tags = array(); + $this->_page = &$this->_createPage($response); + $parser = &$this->_createParser(); + $parser->parse($response->getContent()); + return $this->_page; + } + + /** + * Creates an empty page. + * @return SimplePage New unparsed page. + * @access protected + */ + function &_createPage($response) { + return new SimplePage($response); + } + + /** + * Creates the parser used with the builder. + * @return SimpleSaxParser Parser to generate events for + * the builder. + * @access protected + */ + function &_createParser() { + return new SimpleSaxParser($this); + } + + /** + * Start of element event. Opens a new tag. + * @param string $name Element name. + * @param hash $attributes Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + $tag = &$this->_createTag($name, $attributes); + if ($name == 'form') { + $this->_page->acceptFormStart($tag); + return true; + } + if ($name == 'frameset') { + $this->_page->acceptFramesetStart($tag); + return true; + } + if ($name == 'frame') { + $this->_page->acceptFrame($tag); + return true; + } + if ($tag->expectEndTag()) { + $this->_openTag($tag); + return true; + } + $this->_page->acceptTag($tag); + return true; + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + if ($name == 'form') { + $this->_page->acceptFormEnd(); + return true; + } + if ($name == 'frameset') { + $this->_page->acceptFramesetEnd(); + return true; + } + if (isset($this->_tags[$name]) && (count($this->_tags[$name]) > 0)) { + $tag = array_pop($this->_tags[$name]); + $this->_addContentTagToOpenTags($tag); + $this->_page->acceptTag($tag); + return true; + } + return true; + } + + /** + * Unparsed, but relevant data. The data is added + * to every open tag. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + foreach (array_keys($this->_tags) as $name) { + for ($i = 0; $i < count($this->_tags[$name]); $i++) { + $this->_tags[$name][$i]->addContent($text); + } + } + return true; + } + + /** + * Parsed relevant data. The parsed tag is added + * to every open tag. + * @param SimpleTag $tag May include unparsed tags. + * @access private + */ + function _addContentTagToOpenTags(&$tag) { + if (! in_array($tag->getTagName(), array('option'))) { + return; + } + foreach (array_keys($this->_tags) as $name) { + for ($i = 0; $i < count($this->_tags[$name]); $i++) { + $this->_tags[$name][$i]->addTag($tag); + } + } + } + + /** + * Opens a tag for receiving content. Multiple tags + * will be receiving input at the same time. + * @param SimpleTag $tag New content tag. + * @access private + */ + function _openTag(&$tag) { + $name = $tag->getTagName(); + if (! in_array($name, array_keys($this->_tags))) { + $this->_tags[$name] = array(); + } + array_push($this->_tags[$name], $tag); + } + + /** + * Factory for the tag objects. Creates the + * appropriate tag object for the incoming tag name. + * @param string $name HTML tag name. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + function &_createTag($name, $attributes) { + if ($name == 'a') { + return new SimpleAnchorTag($attributes); + } elseif ($name == 'title') { + return new SimpleTitleTag($attributes); + } elseif ($name == 'input') { + return $this->_createInputTag($attributes); + } elseif ($name == 'button') { + return new SimpleButtonTag($attributes); + } elseif ($name == 'textarea') { + return new SimpleTextAreaTag($attributes); + } elseif ($name == 'select') { + return $this->_createSelectionTag($attributes); + } elseif ($name == 'option') { + return new SimpleOptionTag($attributes); + } elseif ($name == 'form') { + return new SimpleFormTag($attributes); + } elseif ($name == 'frame') { + return new SimpleFrameTag($attributes); + } + return new SimpleTag($name, $attributes); + } + + /** + * Factory for selection fields. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + function &_createSelectionTag($attributes) { + if (isset($attributes['multiple'])) { + return new MultipleSelectionTag($attributes); + } + return new SimpleSelectionTag($attributes); + } + + /** + * Factory for input tags. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + function &_createInputTag($attributes) { + if (! isset($attributes['type'])) { + return new SimpleTextTag($attributes); + } + if ($attributes['type'] == 'submit') { + return new SimpleSubmitTag($attributes); + } elseif ($attributes['type'] == 'image') { + return new SimpleImageSubmitTag($attributes); + } elseif ($attributes['type'] == 'checkbox') { + return new SimpleCheckboxTag($attributes); + } elseif ($attributes['type'] == 'radio') { + return new SimpleRadioButtonTag($attributes); + } else { + return new SimpleTextTag($attributes); + } + } + } + + /** + * A wrapper for a web page. + * @package SimpleTest + * @subpackage WebTester + */ + class SimplePage { + var $_links; + var $_title; + var $_open_forms; + var $_complete_forms; + var $_frameset; + var $_frames; + var $_frameset_nesting_level; + var $_transport_error; + var $_raw; + var $_sent; + var $_headers; + var $_method; + var $_url; + var $_request_data; + + /** + * Parses a page ready to access it's contents. + * @param SimpleHttpResponse $response Result of HTTP fetch. + * @access public + */ + function SimplePage($response = false) { + $this->_links = array(); + $this->_title = false; + $this->_open_forms = array(); + $this->_complete_forms = array(); + $this->_frameset = false; + $this->_frames = array(); + $this->_frameset_nesting_level = 0; + if ($response) { + $this->_extractResponse($response); + } else { + $this->_noResponse(); + } + } + + /** + * Extracts all of the response information. + * @param SimpleHttpResponse $response Response being parsed. + * @access private + */ + function _extractResponse($response) { + $this->_transport_error = $response->getError(); + $this->_raw = $response->getContent(); + $this->_sent = $response->getSent(); + $this->_headers = $response->getHeaders(); + $this->_method = $response->getMethod(); + $this->_url = $response->getUrl(); + $this->_request_data = $response->getRequestData(); + } + + /** + * Sets up a missng response. + * @access private + */ + function _noResponse() { + $this->_transport_error = 'No page fetched yet'; + $this->_raw = false; + $this->_sent = false; + $this->_headers = false; + $this->_method = 'GET'; + $this->_url = false; + $this->_request_data = false; + } + + /** + * Original request as bytes sent down the wire. + * @return mixed Sent content. + * @access public + */ + function getRequest() { + return $this->_sent; + } + + /** + * Accessor for raw text of page. + * @return string Raw unparsed content. + * @access public + */ + function getRaw() { + return $this->_raw; + } + + /** + * Accessor for raw headers of page. + * @return string Header block as text. + * @access public + */ + function getHeaders() { + if ($this->_headers) { + return $this->_headers->getRaw(); + } + return false; + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->_method; + } + + /** + * Original resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->_url; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->_request_data; + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + return $this->_transport_error; + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + if ($this->_headers) { + return $this->_headers->getMimeType(); + } + return false; + } + + /** + * Accessor for HTTP response code. + * @return integer HTTP response code received. + * @access public + */ + function getResponseCode() { + if ($this->_headers) { + return $this->_headers->getResponseCode(); + } + return false; + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + if ($this->_headers) { + return $this->_headers->getAuthentication(); + } + return false; + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + if ($this->_headers) { + return $this->_headers->getRealm(); + } + return false; + } + + /** + * Accessor for current frame focus. Will be + * false as no frames. + * @return array Always empty. + * @access public + */ + function getFrameFocus() { + return array(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean Always false. + * @access public + */ + function setFrameFocusByIndex($choice) { + return false; + } + + /** + * Sets the focus by name. Always fails for a leaf page. + * @param string $name Chosen frame. + * @return boolean False as no frames. + * @access public + */ + function setFrameFocus($name) { + return false; + } + + /** + * Clears the frame focus. Does nothing for a leaf page. + * @access public + */ + function clearFrameFocus() { + } + + /** + * Adds a tag to the page. + * @param SimpleTag $tag Tag to accept. + * @access public + */ + function acceptTag(&$tag) { + if ($tag->getTagName() == "a") { + $this->_addLink($tag); + } elseif ($tag->getTagName() == "title") { + $this->_setTitle($tag); + } elseif ($this->_isFormElement($tag->getTagName())) { + for ($i = 0; $i < count($this->_open_forms); $i++) { + $this->_open_forms[$i]->addWidget($tag); + } + } + } + + /** + * Tests to see if a tag is a possible form + * element. + * @param string $name HTML element name. + * @return boolean True if form element. + * @access private + */ + function _isFormElement($name) { + return in_array($name, array('input', 'button', 'textarea', 'select')); + } + + /** + * Opens a form. New widgets go here. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + function acceptFormStart(&$tag) { + $this->_open_forms[] = &new SimpleForm($tag, $this->getUrl()); + } + + /** + * Closes the most recently opened form. + * @access public + */ + function acceptFormEnd() { + if (count($this->_open_forms)) { + $this->_complete_forms[] = array_pop($this->_open_forms); + } + } + + /** + * Opens a frameset. A frameset may contain nested + * frameset tags. + * @param SimpleFramesetTag $tag Tag to accept. + * @access public + */ + function acceptFramesetStart(&$tag) { + if (! $this->_isLoadingFrames()) { + $this->_frameset = &$tag; + } + $this->_frameset_nesting_level++; + } + + /** + * Closes the most recently opened frameset. + * @access public + */ + function acceptFramesetEnd() { + if ($this->_isLoadingFrames()) { + $this->_frameset_nesting_level--; + } + } + + /** + * Takes a single frame tag and stashes it in + * the current frame set. + * @param SimpleFrameTag $tag Tag to accept. + * @access public + */ + function acceptFrame(&$tag) { + if ($this->_isLoadingFrames()) { + if ($tag->getAttribute('src')) { + $this->_frames[] = &$tag; + } + } + } + + /** + * Test to see if in the middle of reading + * a frameset. + * @return boolean True if inframeset. + * @access private + */ + function _isLoadingFrames() { + if (! $this->_frameset) { + return false; + } + return ($this->_frameset_nesting_level > 0); + } + + /** + * Test to see if link is an absolute one. + * @param string $url Url to test. + * @return boolean True if absolute. + * @access protected + */ + function _linkIsAbsolute($url) { + $parsed = new SimpleUrl($url); + return (boolean)($parsed->getScheme() && $parsed->getHost()); + } + + /** + * Adds a link to the page. + * @param SimpleAnchorTag $tag Link to accept. + * @access protected + */ + function _addLink($tag) { + $this->_links[] = $tag; + } + + /** + * Test for the presence of a frameset. + * @return boolean True if frameset. + * @access public + */ + function hasFrames() { + return (boolean)$this->_frameset; + } + + /** + * Accessor for frame name and source URL for every frame that + * will need to be loaded. Immediate children only. + * @return boolean/array False if no frameset or + * otherwise a hash of frame URLs. + * The key is either a numerical + * base one index or the name attribute. + * @access public + */ + function getFrameset() { + if (! $this->_frameset) { + return false; + } + $urls = array(); + for ($i = 0; $i < count($this->_frames); $i++) { + $name = $this->_frames[$i]->getAttribute('name'); + $url = new SimpleUrl($this->_frames[$i]->getAttribute('src')); + $urls[$name ? $name : $i + 1] = $url->makeAbsolute($this->getUrl()); + } + return $urls; + } + + /** + * Fetches a list of loaded frames. + * @return array/string Just the URL for a single + * page. + * @access public + */ + function getFrames() { + $url = $this->getUrl(); + return $url->asString(); + } + + /** + * Accessor for a list of all fixed links. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getAbsoluteUrls() { + $all = array(); + foreach ($this->_links as $link) { + if ($this->_linkIsAbsolute($link->getHref())) { + $all[] = $link->getHref(); + } + } + return $all; + } + + /** + * Accessor for a list of all relative links. + * @return array List of urls without hostname. + * @access public + */ + function getRelativeUrls() { + $all = array(); + foreach ($this->_links as $link) { + if (! $this->_linkIsAbsolute($link->getHref())) { + $all[] = $link->getHref(); + } + } + return $all; + } + + /** + * Space at the ends will be stripped and space in + * between is reduced to one space. + * @param string $html Typical HTML code. + * @return string Content as big string. + * @access private + */ + function _normalise($html) { + return preg_replace('/\S\s+\S/', ' ', strtolower(trim($html))); + } + + /** + * Matches strings regardles of varying whitespace. + * @param string $first First to match with. + * @param string $second Second to match against. + * @return boolean True if matches even with + * whitespace differences. + * @access private + */ + function _isNormalMatch($first, $second) { + return ($this->_normalise($first) == $this->_normalise($second)); + } + + /** + * Accessor for URLs by the link label. Label will match + * regardess of whitespace issues and case. + * @param string $label Text of link. + * @return array List of links with that label. + * @access public + */ + function getUrlsByLabel($label) { + $matches = array(); + foreach ($this->_links as $link) { + if ($this->_isNormalMatch($link->getContent(), $label)) { + $matches[] = $this->_getUrlFromLink($link); + } + } + return $matches; + } + + /** + * Accessor for a URL by the id attribute. + * @param string $id Id attribute of link. + * @return SimpleUrl URL with that id of false if none. + * @access public + */ + function getUrlById($id) { + foreach ($this->_links as $link) { + if ($link->getAttribute('id') === (string)$id) { + return $this->_getUrlFromLink($link); + } + } + return false; + } + + /** + * Converts a link into a target URL. + * @param SimpleAnchor $link Parsed link. + * @return SimpleUrl URL with frame target if any. + * @access private + */ + function _getUrlFromLink($link) { + $url = $this->_makeAbsolute($link->getHref()); + if ($link->getAttribute('target')) { + $url->setTarget($link->getAttribute('target')); + } + return $url; + } + + /** + * Expands expandomatic URLs into fully qualified + * URLs. + * @param SimpleUrl $url Relative URL. + * @return SimpleUrl Absolute URL. + * @access protected + */ + function _makeAbsolute($url) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + return $url->makeAbsolute($this->getUrl()); + } + + /** + * Sets the title tag contents. + * @param SimpleTitleTag $tag Title of page. + * @access protected + */ + function _setTitle(&$tag) { + $this->_title = &$tag; + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + if ($this->_title) { + return $this->_title->getContent(); + } + return false; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. + * @param string $label Button label, default 'Submit'. + * @return SimpleForm Form object containing the button. + * @access public + */ + function &getFormBySubmitLabel($label) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasSubmitLabel($label)) { + return $this->_complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. + * @param string $name Button name attribute. + * @return SimpleForm Form object containing the button. + * @access public + */ + function &getFormBySubmitName($name) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasSubmitName($name)) { + return $this->_complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by button id. Will only + * search correctly built forms. + * @param string $id Button ID attribute. + * @return SimpleForm Form object containing the button. + * @access public + */ + function &getFormBySubmitId($id) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasSubmitId($id)) { + return $this->_complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by image label. Will only + * search correctly built forms. + * @param string $label Usually the alt attribute. + * @return SimpleForm Form object containing the image. + * @access public + */ + function &getFormByImageLabel($label) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasImageLabel($label)) { + return $this->_complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by image button id. Will only + * search correctly built forms. + * @param string $name Image name. + * @return SimpleForm Form object containing the image. + * @access public + */ + function &getFormByImageName($name) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasImageName($name)) { + return $this->_complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by image button id. Will only + * search correctly built forms. + * @param string $id Image ID attribute. + * @return SimpleForm Form object containing the image. + * @access public + */ + function &getFormByImageId($id) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasImageId($id)) { + return $this->_complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by the form ID. A way of + * identifying a specific form when we have control + * of the HTML code. + * @param string $id Form label. + * @return SimpleForm Form object containing the matching ID. + * @access public + */ + function &getFormById($id) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->getId() == $id) { + return $this->_complete_forms[$i]; + } + } + return null; + } + + /** + * Sets a field on each form in which the field is + * available. + * @param string $name Field name. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setField($name, $value) { + $is_set = false; + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->setField($name, $value)) { + $is_set = true; + } + } + return $is_set; + } + + /** + * Sets a field on the form in which the unique field is + * available. + * @param string/integer $id Field ID attribute. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setFieldById($id, $value) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->setFieldById($id, $value)) { + return true; + } + } + return false; + } + + /** + * Accessor for a form element value within a page. + * Finds the first match. + * @param string $name Field name. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($name) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + $value = $this->_complete_forms[$i]->getValue($name); + if (isset($value)) { + return $value; + } + } + return null; + } + + /** + * Accessor for a form element value within a page. + * Finds the first match. + * @param string/integer $id Field ID attribute. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getFieldById($id) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + $value = $this->_complete_forms[$i]->getValueById($id); + if (isset($value)) { + return $value; + } + } + return null; + } + } +?> diff --git a/htdocs/TESTS/simpletest/parser.php b/htdocs/TESTS/simpletest/parser.php new file mode 100644 index 0000000..6c56731 --- /dev/null +++ b/htdocs/TESTS/simpletest/parser.php @@ -0,0 +1,728 @@ +_case = $case; + $this->_patterns = array(); + $this->_labels = array(); + $this->_regex = null; + } + + /** + * Adds a pattern with an optional label. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $label Label of regex to be returned + * on a match. + * @access public + */ + function addPattern($pattern, $label = true) { + $count = count($this->_patterns); + $this->_patterns[$count] = $pattern; + $this->_labels[$count] = $label; + $this->_regex = null; + } + + /** + * Attempts to match all patterns at once against + * a string. + * @param string $subject String to match against. + * @param string $match First matched portion of + * subject. + * @return boolean True on success. + * @access public + */ + function match($subject, &$match) { + if (count($this->_patterns) == 0) { + return false; + } + if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) { + $match = ''; + return false; + } + $match = $matches[0]; + for ($i = 1; $i < count($matches); $i++) { + if ($matches[$i]) { + return $this->_labels[$i - 1]; + } + } + return true; + } + + /** + * Compounds the patterns into a single + * regular expression separated with the + * "or" operator. Caches the regex. + * Will automatically escape (, ) and / tokens. + * @param array $patterns List of patterns in order. + * @access private + */ + function _getCompoundedRegex() { + if ($this->_regex == null) { + for ($i = 0; $i < count($this->_patterns); $i++) { + $this->_patterns[$i] = '(' . str_replace( + array('/', '(', ')'), + array('\/', '\(', '\)'), + $this->_patterns[$i]) . ')'; + } + $this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags(); + } + return $this->_regex; + } + + /** + * Accessor for perl regex mode flags to use. + * @return string Perl regex flags. + * @access private + */ + function _getPerlMatchingFlags() { + return ($this->_case ? "msS" : "msSi"); + } + } + + /** + * States for a stack machine. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleStateStack { + var $_stack; + + /** + * Constructor. Starts in named state. + * @param string $start Starting state name. + * @access public + */ + function SimpleStateStack($start) { + $this->_stack = array($start); + } + + /** + * Accessor for current state. + * @return string State. + * @access public + */ + function getCurrent() { + return $this->_stack[count($this->_stack) - 1]; + } + + /** + * Adds a state to the stack and sets it + * to be the current state. + * @param string $state New state. + * @access public + */ + function enter($state) { + array_push($this->_stack, $state); + } + + /** + * Leaves the current state and reverts + * to the previous one. + * @return boolean False if we drop off + * the bottom of the list. + * @access public + */ + function leave() { + if (count($this->_stack) == 1) { + return false; + } + array_pop($this->_stack); + return true; + } + } + + /** + * Accepts text and breaks it into tokens. + * Some optimisation to make the sure the + * content is only scanned by the PHP regex + * parser once. Lexer modes must not start + * with leading underscores. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleLexer { + var $_regexes; + var $_parser; + var $_mode; + var $_mode_handlers; + var $_case; + + /** + * Sets up the lexer in case insensitive matching + * by default. + * @param SimpleSaxParser $parser Handling strategy by + * reference. + * @param string $start Starting handler. + * @param boolean $case True for case sensitive. + * @access public + */ + function SimpleLexer(&$parser, $start = "accept", $case = false) { + $this->_case = $case; + $this->_regexes = array(); + $this->_parser = &$parser; + $this->_mode = &new SimpleStateStack($start); + $this->_mode_handlers = array($start => $start); + } + + /** + * Adds a token search pattern for a particular + * parsing mode. The pattern does not change the + * current mode. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @access public + */ + function addPattern($pattern, $mode = "accept") { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern); + if (! isset($this->_mode_handlers[$mode])) { + $this->_mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that will enter a new parsing + * mode. Useful for entering parenthesis, strings, + * tags, etc. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $new_mode Change parsing to this new + * nested mode. + * @access public + */ + function addEntryPattern($pattern, $mode, $new_mode) { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern, $new_mode); + if (! isset($this->_mode_handlers[$new_mode])) { + $this->_mode_handlers[$new_mode] = $new_mode; + } + } + + /** + * Adds a pattern that will exit the current mode + * and re-enter the previous one. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Mode to leave. + * @access public + */ + function addExitPattern($pattern, $mode) { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern, "__exit"); + if (! isset($this->_mode_handlers[$mode])) { + $this->_mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that has a special mode. Acts as an entry + * and exit pattern in one go, effectively calling a special + * parser handler for this token only. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $special Use this mode for this one token. + * @access public + */ + function addSpecialPattern($pattern, $mode, $special) { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern, "_$special"); + if (! isset($this->_mode_handlers[$special])) { + $this->_mode_handlers[$special] = $special; + } + } + + /** + * Adds a mapping from a mode to another handler. + * @param string $mode Mode to be remapped. + * @param string $handler New target handler. + * @access public + */ + function mapHandler($mode, $handler) { + $this->_mode_handlers[$mode] = $handler; + } + + /** + * Splits the page text into tokens. Will fail + * if the handlers report an error or if no + * content is consumed. If successful then each + * unparsed and parsed token invokes a call to the + * held listener. + * @param string $raw Raw HTML text. + * @return boolean True on success, else false. + * @access public + */ + function parse($raw) { + if (! isset($this->_parser)) { + return false; + } + $length = strlen($raw); + while (is_array($parsed = $this->_reduce($raw))) { + list($raw, $unmatched, $matched, $mode) = $parsed; + if (! $this->_dispatchTokens($unmatched, $matched, $mode)) { + return false; + } + if ($raw === '') { + return true; + } + if (strlen($raw) == $length) { + return false; + } + $length = strlen($raw); + } + if (! $parsed) { + return false; + } + return $this->_invokeParser($raw, LEXER_UNMATCHED); + } + + /** + * Sends the matched token and any leading unmatched + * text to the parser changing the lexer to a new + * mode if one is listed. + * @param string $unmatched Unmatched leading portion. + * @param string $matched Actual token match. + * @param string $mode Mode after match. A boolean + * false mode causes no change. + * @return boolean False if there was any error + * from the parser. + * @access private + */ + function _dispatchTokens($unmatched, $matched, $mode = false) { + if (! $this->_invokeParser($unmatched, LEXER_UNMATCHED)) { + return false; + } + if (is_bool($mode)) { + return $this->_invokeParser($matched, LEXER_MATCHED); + } + if ($this->_isModeEnd($mode)) { + if (! $this->_invokeParser($matched, LEXER_EXIT)) { + return false; + } + return $this->_mode->leave(); + } + if ($this->_isSpecialMode($mode)) { + $this->_mode->enter($this->_decodeSpecial($mode)); + if (! $this->_invokeParser($matched, LEXER_SPECIAL)) { + return false; + } + return $this->_mode->leave(); + } + $this->_mode->enter($mode); + return $this->_invokeParser($matched, LEXER_ENTER); + } + + /** + * Tests to see if the new mode is actually to leave + * the current mode and pop an item from the matching + * mode stack. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + function _isModeEnd($mode) { + return ($mode === "__exit"); + } + + /** + * Test to see if the mode is one where this mode + * is entered for this token only and automatically + * leaves immediately afterwoods. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + function _isSpecialMode($mode) { + return (strncmp($mode, "_", 1) == 0); + } + + /** + * Strips the magic underscore marking single token + * modes. + * @param string $mode Mode to decode. + * @return string Underlying mode name. + * @access private + */ + function _decodeSpecial($mode) { + return substr($mode, 1); + } + + /** + * Calls the parser method named after the current + * mode. Empty content will be ignored. The lexer + * has a parser handler for each mode in the lexer. + * @param string $content Text parsed. + * @param boolean $is_match Token is recognised rather + * than unparsed data. + * @access private + */ + function _invokeParser($content, $is_match) { + if (($content === '') || ($content === false)) { + return true; + } + $handler = $this->_mode_handlers[$this->_mode->getCurrent()]; + return $this->_parser->$handler($content, $is_match); + } + + /** + * Tries to match a chunk of text and if successful + * removes the recognised chunk and any leading + * unparsed data. Empty strings will not be matched. + * @param string $raw The subject to parse. This is the + * content that will be eaten. + * @return array Three item list of unparsed + * content followed by the + * recognised token and finally the + * action the parser is to take. + * True if no match, false if there + * is a parsing error. + * @access private + */ + function _reduce($raw) { + if ($action = $this->_regexes[$this->_mode->getCurrent()]->match($raw, $match)) { + $unparsed_character_count = strpos($raw, $match); + $unparsed = substr($raw, 0, $unparsed_character_count); + $raw = substr($raw, $unparsed_character_count + strlen($match)); + return array($raw, $unparsed, $match, $action); + } + return true; + } + } + + /** + * Converts HTML tokens into selected SAX events. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleSaxParser { + var $_lexer; + var $_listener; + var $_tag; + var $_attributes; + var $_current_attribute; + + /** + * Sets the listener. + * @param SimpleSaxListener $listener SAX event handler. + * @access public + */ + function SimpleSaxParser(&$listener) { + $this->_listener = &$listener; + $this->_lexer = &$this->createLexer($this); + $this->_tag = ''; + $this->_attributes = array(); + $this->_current_attribute = ''; + } + + /** + * Runs the content through the lexer which + * should call back to the acceptors. + * @param string $raw Page text to parse. + * @return boolean False if parse error. + * @access public + */ + function parse($raw) { + return $this->_lexer->parse($raw); + } + + /** + * Sets up the matching lexer. Starts in 'text' mode. + * @param SimpleSaxParser $parser Event generator, usually $self. + * @return SimpleLexer Lexer suitable for this parser. + * @access public + * @static + */ + function &createLexer(&$parser) { + $lexer = &new SimpleLexer($parser, 'text'); + $lexer->mapHandler('text', 'acceptTextToken'); + SimpleSaxParser::_addSkipping($lexer); + foreach (SimpleSaxParser::_getParsedTags() as $tag) { + SimpleSaxParser::_addTag($lexer, $tag); + } + SimpleSaxParser::_addInTagTokens($lexer); + return $lexer; + } + + /** + * List of parsed tags. Others are ignored. + * @return array List of searched for tags. + * @access private + */ + function _getParsedTags() { + return array('a', 'title', 'form', 'input', 'button', 'textarea', 'select', + 'option', 'frameset', 'frame'); + } + + /** + * The lexer has to skip certain sections such + * as server code, client code and styles. + * @param SimpleLexer $lexer Lexer to add patterns to. + * @access private + * @static + */ + function _addSkipping(&$lexer) { + $lexer->mapHandler('css', 'ignore'); + $lexer->addEntryPattern('addExitPattern('', 'css'); + $lexer->mapHandler('js', 'ignore'); + $lexer->addEntryPattern('addExitPattern('', 'js'); + $lexer->mapHandler('comment', 'ignore'); + $lexer->addEntryPattern('', 'comment'); + } + + /** + * Pattern matches to start and end a tag. + * @param SimpleLexer $lexer Lexer to add patterns to. + * @param string $tag Name of tag to scan for. + * @access private + * @static + */ + function _addTag(&$lexer, $tag) { + $lexer->addSpecialPattern("", 'text', 'acceptEndToken'); + $lexer->addEntryPattern("<$tag", 'text', 'tag'); + } + + /** + * Pattern matches to parse the inside of a tag + * including the attributes and their quoting. + * @param SimpleLexer $lexer Lexer to add patterns to. + * @access private + * @static + */ + function _addInTagTokens(&$lexer) { + $lexer->mapHandler('tag', 'acceptStartToken'); + $lexer->addSpecialPattern('\s+', 'tag', 'ignore'); + SimpleSaxParser::_addAttributeTokens($lexer); + $lexer->addExitPattern('>', 'tag'); + } + + /** + * Matches attributes that are either single quoted, + * double quoted or unquoted. + * @param SimpleLexer $lexer Lexer to add patterns to. + * @access private + * @static + */ + function _addAttributeTokens(&$lexer) { + $lexer->mapHandler('dq_attribute', 'acceptAttributeToken'); + $lexer->addEntryPattern('=\s*"', 'tag', 'dq_attribute'); + $lexer->addPattern("\\\\\"", 'dq_attribute'); + $lexer->addExitPattern('"', 'dq_attribute'); + $lexer->mapHandler('sq_attribute', 'acceptAttributeToken'); + $lexer->addEntryPattern("=\s*'", 'tag', 'sq_attribute'); + $lexer->addPattern("\\\\'", 'sq_attribute'); + $lexer->addExitPattern("'", 'sq_attribute'); + $lexer->mapHandler('uq_attribute', 'acceptAttributeToken'); + $lexer->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute'); + } + + /** + * Accepts a token from the tag mode. If the + * starting element completes then the element + * is dispatched and the current attributes + * set back to empty. The element or attribute + * name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptStartToken($token, $event) { + if ($event == LEXER_ENTER) { + $this->_tag = strtolower(substr($token, 1)); + return true; + } + if ($event == LEXER_EXIT) { + $success = $this->_listener->startElement( + $this->_tag, + $this->_attributes); + $this->_tag = ""; + $this->_attributes = array(); + return $success; + } + if ($token != "=") { + $this->_current_attribute = strtolower($this->_decodeHtml($token)); + $this->_attributes[$this->_current_attribute] = ""; + } + return true; + } + + /** + * Accepts a token from the end tag mode. + * The element name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEndToken($token, $event) { + if (! preg_match('/<\/(.*)>/', $token, $matches)) { + return false; + } + return $this->_listener->endElement(strtolower($matches[1])); + } + + /** + * Part of the tag data. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptAttributeToken($token, $event) { + if ($event == LEXER_UNMATCHED) { + $this->_attributes[$this->_current_attribute] .= + $this->_decodeHtml($token); + } + if ($event == LEXER_SPECIAL) { + $this->_attributes[$this->_current_attribute] .= + preg_replace('/^=\s*/' , '', $this->_decodeHtml($token)); + } + return true; + } + + /** + * A character entity. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEntityToken($token, $event) { + } + + /** + * Character data between tags regarded as + * important. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptTextToken($token, $event) { + return $this->_listener->addContent($token); + } + + /** + * Incoming data to be ignored. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function ignore($token, $event) { + return true; + } + + /** + * Decodes any HTML entities. + * @param string $html Incoming HTML. + * @return string Outgoing plain text. + * @access private + */ + function _decodeHtml($html) { + return strtr( + $html, + array_flip(get_html_translation_table(HTML_ENTITIES))); + } + } + + /** + * SAX event handler. + * @package SimpleTest + * @subpackage WebTester + * @abstract + */ + class SimpleSaxListener { + + /** + * Sets the document to write to. + * @access public + */ + function SimpleSaxListener() { + } + + /** + * Start of element event. + * @param string $name Element name. + * @param hash $attributes Name value pairs. + * Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + } + + /** + * Unparsed, but relevant data. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/remote.php b/htdocs/TESTS/simpletest/remote.php new file mode 100644 index 0000000..50165a7 --- /dev/null +++ b/htdocs/TESTS/simpletest/remote.php @@ -0,0 +1,115 @@ +_url = $url; + $this->_dry_url = $dry_url ? $dry_url : $url; + $this->_size = false; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->_url; + } + + /** + * Runs the top level test for this class. Currently + * reads the data as a single chunk. I'll fix this + * once I have added iteration to the browser. + * @param SimpleReporter $reporter Target of test results. + * @returns boolean True if no failures. + * @access public + */ + function run(&$reporter) { + $browser = &$this->_createBrowser(); + $xml = $browser->get($this->_url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->_url . ']'); + return false; + } + $parser = &$this->_createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->_url . ']'); + return false; + } + return true; + } + + /** + * Creates a new web browser object for fetching + * the XML report. + * @return SimpleBrowser New browser. + * @access protected + */ + function &_createBrowser() { + return new SimpleBrowser(); + } + + /** + * Creates the XML parser. + * @param SimpleReporter $reporter Target of test results. + * @return SimpleTestXmlListener XML reader. + * @access protected + */ + function &_createParser(&$reporter) { + return new SimpleTestXmlParser($reporter); + } + + /** + * Accessor for the number of subtests. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + if ($this->_size === false) { + $browser = &$this->_createBrowser(); + $xml = $browser->get($this->_dry_url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->_dry_url . ']'); + return false; + } + $reporter = &new SimpleReporter(); + $parser = &$this->_createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->_dry_url . ']'); + return false; + } + $this->_size = $reporter->getTestCaseCount(); + } + return $this->_size; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/reporter.php b/htdocs/TESTS/simpletest/reporter.php new file mode 100644 index 0000000..c44cfbd --- /dev/null +++ b/htdocs/TESTS/simpletest/reporter.php @@ -0,0 +1,227 @@ +SimpleReporter(); + } + + /** + * Paints the top of the web page setting the + * title to the name of the starting test. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + $this->sendNoCacheHeaders(); + print "\n\n$test_name\n"; + print "\n"; + print "\n\n"; + print "

$test_name

\n"; + flush(); + } + + /** + * Send the headers necessary to ensure the page is + * reloaded on every request. Otherwise you could be + * scratching your head over out of date test data. + * @access public + * @static + */ + function sendNoCacheHeaders() { + if (! headers_sent()) { + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + } + } + + /** + * Paints the CSS. Add additional styles here. + * @return string CSS code as text. + * @access protected + */ + function _getCss() { + return ".fail { color: red; } pre { background-color: lightgray; }"; + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green"); + print "
"; + print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount(); + print " test cases complete:\n"; + print "" . $this->getPassCount() . " passes, "; + print "" . $this->getFailCount() . " fails and "; + print "" . $this->getExceptionCount() . " exceptions."; + print "
\n"; + print "\n\n"; + } + + /** + * Paints the test failure with a breadcrumbs + * trail of the nesting test suites below the + * top level test. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print "Fail: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . htmlentities($message) . "
\n"; + } + + /** + * Paints a PHP error or exception. + * @param string $message Message is ignored. + * @access public + * @abstract + */ + function paintException($message) { + parent::paintException($message); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . htmlentities($message) . "
\n"; + } + + /** + * Paints formatted text such as dumped variables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + echo '
', htmlentities($message), '
'; + } + } + + /** + * Sample minimal test displayer. Generates only + * failure messages and a pass count. For command + * line use. I've tried to make it look like JUnit, + * but I wanted to output the errors as they arrived + * which meant dropping the dots. + * @package SimpleTest + * @subpackage UnitTester + */ + class TextReporter extends SimpleReporter { + + /** + * Does nothing yet. The first output will + * be sent on the first test start. + * @access public + */ + function TextReporter() { + $this->SimpleReporter(); + } + + /** + * Paints the title only. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + if (!SimpleReporter::inCli()) { + header('Content-type: text/plain'); + } + print "$test_name\n"; + flush(); + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + if ($this->getFailCount() + $this->getExceptionCount() == 0) { + print "OK\n"; + } else { + print "FAILURES!!!\n"; + } + print "Test cases run: " . $this->getTestCaseProgress() . + "/" . $this->getTestCaseCount() . + ", Passes: " . $this->getPassCount() . + ", Failures: " . $this->getFailCount() . + ", Exceptions: " . $this->getExceptionCount() . "\n"; + + } + + /** + * Paints the test failure as a stack trace. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print $this->getFailCount() . ") $message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Paints a PHP error or exception. + * @param string $message Message is ignored. + * @access public + * @abstract + */ + function paintException($message) { + parent::paintException($message); + print "Exception " . $this->getExceptionCount() . "!\n$message\n"; + } + + /** + * Paints formatted text such as dumped variables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + print "$message\n"; + flush(); + } + } +?> diff --git a/htdocs/TESTS/simpletest/scorer.php b/htdocs/TESTS/simpletest/scorer.php new file mode 100644 index 0000000..9d8692c --- /dev/null +++ b/htdocs/TESTS/simpletest/scorer.php @@ -0,0 +1,382 @@ +_passes = 0; + $this->_fails = 0; + $this->_exceptions = 0; + $this->_is_dry_run = false; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + */ + function makeDry($is_dry = true) { + $this->_is_dry_run = $is_dry; + } + + /** + * Invokes a single test method on the test case. + * This call back allows the reporter to decide if + * it actually wants to run the test. + * @param SimpleRunner $runner Test case to run test on. + * @param string $method Name of test method. + * @access public + */ + function invoke(&$runner, $method) { + if (! $this->_is_dry_run) { + $runner->invoke($method); + } + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * Used for command line tools. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + if ($this->_exceptions + $this->_fails > 0) { + return false; + } + return true; + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + } + + /** + * Increments the pass count. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->_passes++; + } + + /** + * Increments the fail count. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->_fails++; + } + + /** + * Deals with PHP 4 throwing an error. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->paintException($message); + } + + /** + * Deals with PHP 5 throwing an exception + * This isn't really implemented yet. + * @param Exception $exception Object thrown. + * @access public + */ + function paintException($exception) { + $this->_exceptions++; + } + + /** + * Accessor for the number of passes so far. + * @return integer Number of passes. + * @access public + */ + function getPassCount() { + return $this->_passes; + } + + /** + * Accessor for the number of fails so far. + * @return integer Number of fails. + * @access public + */ + function getFailCount() { + return $this->_fails; + } + + /** + * Accessor for the number of untrapped errors + * so far. + * @return integer Number of exceptions. + * @access public + */ + function getExceptionCount() { + return $this->_exceptions; + } + + /** + * Paints a simple supplementary message. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + } + + /** + * Paints a formatted ASCII message such as a + * variable dump. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + } + + /** + * By default just ignores user generated events. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @access public + */ + function paintSignal($type, &$payload) { + } + } + + /** + * Recipient of generated test messages that can display + * page footers and headers. Also keeps track of the + * test nesting. This is the main base class on which + * to build the finished test (page based) displays. + * @package SimpleTest + * @subpackage UnitTester + */ + class SimpleReporter extends SimpleScorer { + var $_test_stack; + var $_size; + var $_progress; + + /** + * Starts the display with no results in. + * @access public + */ + function SimpleReporter() { + $this->SimpleScorer(); + $this->_test_stack = array(); + $this->_size = null; + $this->_progress = 0; + } + + /** + * Paints the start of a group test. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + if (! isset($this->_size)) { + $this->_size = $size; + } + if (count($this->_test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->_test_stack[] = $test_name; + } + + /** + * Paints the end of a group test. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @param integer $progress Number of test cases ending. + * @access public + */ + function paintGroupEnd($test_name) { + array_pop($this->_test_stack); + if (count($this->_test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test case. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + if (! isset($this->_size)) { + $this->_size = 1; + } + if (count($this->_test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->_test_stack[] = $test_name; + } + + /** + * Paints the end of a test case. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintCaseEnd($test_name) { + $this->_progress++; + array_pop($this->_test_stack); + if (count($this->_test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + $this->_test_stack[] = $test_name; + } + + /** + * Paints the end of a test method. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintMethodEnd($test_name) { + array_pop($this->_test_stack); + } + + /** + * Paints the test document header. + * @param string $test_name First test top level + * to start. + * @access public + * @abstract + */ + function paintHeader($test_name) { + } + + /** + * Paints the test document footer. + * @param string $test_name The top level test. + * @access public + * @abstract + */ + function paintFooter($test_name) { + } + + /** + * Accessor for internal test stack. For + * subclasses that need to see the whole test + * history for display purposes. + * @return array List of methods in nesting order. + * @access public + */ + function getTestList() { + return $this->_test_stack; + } + + /** + * Accessor for total test size in number + * of test cases. Null until the first + * test is started. + * @return integer Total number of cases at start. + * @access public + */ + function getTestCaseCount() { + return $this->_size; + } + + /** + * Accessor for the number of test cases + * completed so far. + * @return integer Number of ended cases. + * @access public + */ + function getTestCaseProgress() { + return $this->_progress; + } + + /** + * Static check for running in the comand line. + * @return boolean True if CLI. + * @access public + * @static + */ + function inCli() { + return php_sapi_name() == 'cli'; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/shell_tester.php b/htdocs/TESTS/simpletest/shell_tester.php new file mode 100644 index 0000000..1bc1fde --- /dev/null +++ b/htdocs/TESTS/simpletest/shell_tester.php @@ -0,0 +1,271 @@ +_output = false; + } + + /** + * Actually runs the command. Does not trap the + * error stream output as this need PHP 4.3+. + * @param string $command The actual command line + * to run. + * @return integer Exit code. + * @access public + */ + function execute($command) { + $this->_output = false; + exec($command, $this->_output, $ret); + return $ret; + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + return implode("\n", $this->_output); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + return $this->_output; + } + } + + /** + * Test case for testing of command line scripts and + * utilities. Usually scripts taht are external to the + * PHP code, but support it in some way. + * @package SimpleTest + * @subpackage UnitTester + */ + class ShellTestCase extends SimpleTestCase { + var $_current_shell; + var $_last_status; + var $_last_command; + + /** + * Creates an empty test case. Should be subclassed + * with test methods for a functional test case. + * @param string $label Name of test case. Will use + * the class name if none specified. + * @access public + */ + function ShellTestCase($label = false) { + $this->SimpleTestCase($label); + $this->_current_shell = &$this->_createShell(); + $this->_last_status = false; + $this->_last_command = ''; + } + + /** + * Executes a command and buffers the results. + * @param string $command Command to run. + * @return boolean True if zero exit code. + * @access public + */ + function execute($command) { + $shell = &$this->_getShell(); + $this->_last_status = $shell->execute($command); + $this->_last_command = $command; + return ($this->_last_status === 0); + } + + /** + * Dumps the output of the last command. + * @access public + */ + function dumpOutput() { + $this->dump($this->getOutput()); + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + $shell = &$this->_getShell(); + return $shell->getOutput(); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + $shell = &$this->_getShell(); + return $shell->getOutputAsList(); + } + + /** + * Tests the last status code from the shell. + * @param integer $status Expected status of last + * command. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertExitCode($status, $message = "%s") { + $message = sprintf($message, "Expected status code of [$status] from [" . + $this->_last_command . "], but got [" . + $this->_last_status . "]"); + return $this->assertTrue($status === $this->_last_status, $message); + } + + /** + * Attempt to exactly match the combined STDERR and + * STDOUT output. + * @param string $expected Expected output. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutput($expected, $message = "%s") { + $shell = &$this->_getShell(); + return $this->assertExpectation( + new EqualExpectation($expected), + $shell->getOutput(), + $message); + } + + /** + * Scans the output for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutputPattern($pattern, $message = "%s") { + $shell = &$this->_getShell(); + return $this->assertExpectation( + new WantedPatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * If a Perl regex is found anywhere in the current + * output then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoOutputPattern($pattern, $message = "%s") { + $shell = &$this->_getShell(); + return $this->assertExpectation( + new UnwantedPatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * File existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should exist"); + return $this->assertTrue(file_exists($path), $message); + } + + /** + * File non-existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileNotExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should not exist"); + return $this->assertFalse(file_exists($path), $message); + } + + /** + * Scans a file for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFilePattern($pattern, $path, $message = "%s") { + $shell = &$this->_getShell(); + return $this->assertExpectation( + new WantedPatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * If a Perl regex is found anywhere in the named + * file then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoFilePattern($pattern, $path, $message = "%s") { + $shell = &$this->_getShell(); + return $this->assertExpectation( + new UnwantedPatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * Accessor for current shell. Used for testing the + * the tester itself. + * @return Shell Current shell. + * @access protected + */ + function &_getShell() { + return $this->_current_shell; + } + + /** + * Factory for the shell to run the command on. + * @return Shell New shell object. + * @access protected + */ + function &_createShell() { + return new SimpleShell(); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/simple_test.php b/htdocs/TESTS/simpletest/simple_test.php new file mode 100644 index 0000000..44b2dba --- /dev/null +++ b/htdocs/TESTS/simpletest/simple_test.php @@ -0,0 +1,628 @@ +_test_case = &$test_case; + $this->_scorer = &$scorer; + } + + /** + * Accessor for test case being run. + * @return SimpleTestCase Test case. + * @access protected + */ + function &_getTestCase() { + return $this->_test_case; + } + + /** + * Runs the test methods in the test case. + * @param SimpleTest $test_case Test case to run test on. + * @param string $method Name of test method. + * @access public + */ + function run() { + $methods = get_class_methods(get_class($this->_test_case)); + foreach ($methods as $method) { + if (! $this->_isTest($method)) { + continue; + } + if ($this->_isConstructor($method)) { + continue; + } + $this->_scorer->paintMethodStart($method); + $this->_scorer->invoke($this, $method); + $this->_scorer->paintMethodEnd($method); + } + } + + /** + * Tests to see if the method is the constructor and + * so should be ignored. + * @param string $method Method name to try. + * @return boolean True if constructor. + * @access protected + */ + function _isConstructor($method) { + return SimpleTestCompatibility::isA( + $this->_test_case, + strtolower($method)); + } + + /** + * Tests to see if the method is a test that should + * be run. Currently any method that starts with 'test' + * is a candidate. + * @param string $method Method name to try. + * @return boolean True if test method. + * @access protected + */ + function _isTest($method) { + return strtolower(substr($method, 0, 4)) == 'test'; + } + + /** + * Invokes a test method and buffered with setUp() + * and tearDown() calls. + * @param string $method Test method to call. + * @access public + */ + function invoke($method) { + $this->_test_case->before(); + $this->_test_case->setUp(); + $this->_test_case->$method(); + $this->_test_case->tearDown(); + $this->_test_case->after(); + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + $this->_scorer->paintMethodStart($test_name); + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + $this->_scorer->paintMethodEnd($test_name); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->_scorer->paintPass($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->_scorer->paintFail($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->_scorer->paintError($message); + } + + /** + * Chains to the wrapped reporter. + * @param Exception $exception Object thrown. + * @access public + */ + function paintException($exception) { + $this->_scorer->paintException($exception); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + $this->_scorer->paintMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + $this->_scorer->paintFormattedMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @return boolean Should return false if this + * type of signal should fail the + * test suite. + * @access public + */ + function paintSignal($type, &$payload) { + $this->_scorer->paintSignal($type, $payload); + } + } + + /** + * Extension that traps errors into an error queue. + * @package SimpleTest + * @subpackage UnitTester + */ + class SimpleErrorTrappingRunner extends SimpleRunner { + + /** + * Takes in the test case and reporter to mediate between. + * @param SimpleTestCase $test_case Test case to run. + * @param SimpleScorer $scorer Reporter to receive events. + */ + function SimpleErrorTrappingRunner(&$test_case, &$scorer) { + $this->SimpleRunner($test_case, $scorer); + } + + /** + * Invokes a test method and dispatches any + * untrapped errors. Called back from + * the visiting runner. + * @param string $method Test method to call. + * @access public + */ + function invoke($method) { + set_error_handler('simpleTestErrorHandler'); + parent::invoke($method); + $queue = &SimpleErrorQueue::instance(); + while (list($severity, $message, $file, $line, $globals) = $queue->extract()) { + $test_case = &$this->_getTestCase(); + $test_case->error($severity, $message, $file, $line, $globals); + } + restore_error_handler(); + } + } + + /** + * Basic test case. This is the smallest unit of a test + * suite. It searches for + * all methods that start with the the string "test" and + * runs them. Working test cases extend this class. + * @package SimpleTest + * @subpackage UnitTester + */ + class SimpleTestCase { + var $_label; + var $_runner; + + /** + * Sets up the test with no display. + * @param string $label If no test name is given then + * the class name is used. + * @access public + */ + function SimpleTestCase($label = false) { + $this->_label = $label ? $label : get_class($this); + $this->_runner = false; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + * @static + */ + function getLabel() { + return $this->_label; + } + + /** + * Can modify the incoming reporter so as to run + * the tests differently. This version simply + * passes it straight through. + * @param SimpleReporter $reporter Incoming observer. + * @return SimpleReporter + * @access protected + */ + function &_createRunner(&$reporter) { + return new SimpleErrorTrappingRunner($this, $reporter); + } + + /** + * Uses reflection to run every method within itself + * starting with the string "test". + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run(&$reporter) { + $reporter->paintCaseStart($this->getLabel()); + $this->_runner = &$this->_createRunner($reporter); + $this->_runner->run(); + $reporter->paintCaseEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Runs test case specific code before the user setUp(). + * For extension writers not wanting to interfere with setUp(). + * @access protected + */ + function before() { + } + + /** + * Runs test case specific code after the user tearDown(). + * For extension writers not wanting to interfere with user tests. + * @access protected + */ + function after() { + } + + /** + * Sets up unit test wide variables at the start + * of each test method. To be overridden in + * actual user test cases. + * @access public + */ + function setUp() { + } + + /** + * Clears the data set in the setUp() method call. + * To be overridden by the user in actual user test cases. + * @access public + */ + function tearDown() { + } + + /** + * Sends a pass event with a message. + * @param string $message Message to send. + * @access public + */ + function pass($message = "Pass") { + $this->_runner->paintPass($message . $this->getAssertionLine(' at line [%d]')); + } + + /** + * Sends a fail event with a message. + * @param string $message Message to send. + * @access public + */ + function fail($message = "Fail") { + $this->_runner->paintFail($message . $this->getAssertionLine(' at line [%d]')); + } + + /** + * Formats a PHP error and dispatches it to the + * runner. + * @param integer $severity PHP error code. + * @param string $message Text of error. + * @param string $file File error occoured in. + * @param integer $line Line number of error. + * @param hash $globals PHP super global arrays. + * @access public + */ + function error($severity, $message, $file, $line, $globals) { + $severity = SimpleErrorQueue::getSeverityAsString($severity); + $this->_runner->paintError( + "Unexpected PHP error [$message] severity [$severity] in [$file] line [$line]"); + } + + /** + * Sends a user defined event to the test runner. + * This is for small scale extension where + * both the test case and either the runner or + * display are subclassed. + * @param string $type Type of event. + * @param mixed $payload Object or message to deliver. + * @access public + */ + function signal($type, &$payload) { + $this->_runner->paintSignal($type, $payload); + } + + /** + * Cancels any outstanding errors. + * @access public + */ + function swallowErrors() { + $queue = &SimpleErrorQueue::instance(); + $queue->clear(); + } + + /** + * Runs an expectation directly, for extending the + * tests with new expectation classes. + * @param SimpleExpectation $expectation Expectation subclass. + * @param mixed $test_value Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertExpectation(&$expectation, $test_value, $message = '%s') { + return $this->assertTrue( + $expectation->test($test_value), + sprintf($message, $expectation->overlayMessage($test_value))); + } + + /** + * Called from within the test methods to register + * passes and failures. + * @param boolean $result Pass on true. + * @param string $message Message to display describing + * the test state. + * @return boolean True on pass + * @access public + */ + function assertTrue($result, $message = false) { + if (! $message) { + $message = 'True assertion got ' . ($result ? 'True' : 'False'); + } + if ($result) { + $this->pass($message); + return true; + } else { + $this->fail($message); + return false; + } + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = false) { + if (! $message) { + $message = 'False assertion got ' . ($result ? 'True' : 'False'); + } + return ! $this->assertTrue(! $result, $message); + } + + /** + * Uses a stack trace to find the line of an assertion. + * @param string $format String formatting. + * @param array $stack Stack frames top most first. Only + * needed if not using the PHP + * backtrace function. + * @return string Line number of first assert* + * method embedded in format string. + * @access public + */ + function getAssertionLine($format = '%d', $stack = false) { + if ($stack === false) { + $stack = SimpleTestCompatibility::getStackTrace(); + } + return SimpleDumper::getFormattedAssertionLine($stack, $format); + } + + /** + * Sends a formatted dump of a variable to the + * test suite for those emergency debugging + * situations. + * @param mixed $variable Variable to display. + * @param string $message Message to display. + * @return mixed The original variable. + * @access public + */ + function dump($variable, $message = false) { + $formatted = SimpleDumper::dump($variable); + if ($message) { + $formatted = $message . "\n" . $formatted; + } + $this->_runner->paintFormattedMessage($formatted); + return $variable; + } + + /** + * Dispatches a text message straight to the + * test suite. Useful for status bar displays. + * @param string $message Message to show. + * @access public + */ + function sendMessage($message) { + $this->_runner->PaintMessage($message); + } + + /** + * Accessor for the number of subtests. + * @return integer Number of test cases. + * @access public + * @static + */ + function getSize() { + return 1; + } + } + + /** + * This is a composite test class for combining + * test cases and other RunnableTest classes into + * a group test. + * @package SimpleTest + * @subpackage UnitTester + */ + class GroupTest { + var $_label; + var $_test_cases; + + /** + * Sets the name of the test suite. + * @param string $label Name sent at the start and end + * of the test. + * @access public + */ + function GroupTest($label) { + $this->_label = $label; + $this->_test_cases = array(); + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->_label; + } + + /** + * Adds a test into the suite. Can be either a group + * test or some other unit test. + * @param SimpleTestCase $test_case Suite or individual test + * case implementing the + * runnable test interface. + * @access public + */ + function addTestCase(&$test_case) { + $this->_test_cases[] = &$test_case; + } + + /** + * Adds a test into the suite by class name. The class will + * be instantiated as needed. + * @param SimpleTestCase $test_case Suite or individual test + * case implementing the + * runnable test interface. + * @access public + */ + function addTestClass($class) { + $this->_test_cases[] = $class; + } + + /** + * Builds a group test from a library of test cases. + * The new group is composed into this one. + * @param string $test_file File name of library with + * test case classes. + * @access public + */ + function addTestFile($test_file) { + $existing_classes = get_declared_classes(); + require($test_file); + $group = new GroupTest($test_file); + foreach (get_declared_classes() as $class) { + if (in_array($class, $existing_classes)) { + continue; + } + if (! $this->_isTestCase($class)) { + continue; + } + if (SimpleTestOptions::isIgnored($class)) { + continue; + } + $group->addTestClass($class); + } + $this->addTestCase($group); + } + + /** + * Test to see if a class is derived from the + * TestCase class. + * @param string $class Class name. + * @access private + */ + function _isTestCase($class) { + while ($class = get_parent_class($class)) { + if (strtolower($class) == "simpletestcase") { + return true; + } + } + return false; + } + + /** + * Invokes run() on all of the held test cases, instantiating + * them if necessary. + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run(&$reporter) { + $reporter->paintGroupStart($this->getLabel(), $this->getSize()); + for ($i = 0; $i < count($this->_test_cases); $i++) { + if (is_string($this->_test_cases[$i])) { + $class = $this->_test_cases[$i]; + $test = &new $class(); + $test->run($reporter); + } else { + $this->_test_cases[$i]->run($reporter); + } + } + $reporter->paintGroupEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Number of contained test cases. + * @return integer Total count of cases in the group. + * @access public + */ + function getSize() { + $count = 0; + foreach ($this->_test_cases as $case) { + if (is_string($case)) { + $count++; + } else { + $count += $case->getSize(); + } + } + return $count; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/socket.php b/htdocs/TESTS/simpletest/socket.php new file mode 100644 index 0000000..9f3900d --- /dev/null +++ b/htdocs/TESTS/simpletest/socket.php @@ -0,0 +1,214 @@ +_clearError(); + } + + /** + * Test for an outstanding error. + * @return boolean True if there is an error. + * @access public + */ + function isError() { + return ($this->_error != ''); + } + + /** + * Accessor for an outstanding error. + * @return string Empty string if no error otherwise + * the error message. + * @access public + */ + function getError() { + return $this->_error; + } + + /** + * Sets the internal error. + * @param string Error message to stash. + * @access protected + */ + function _setError($error) { + $this->_error = $error; + } + + /** + * Resets the error state to no error. + * @access protected + */ + function _clearError() { + $this->_setError(''); + } + } + + /** + * Wrapper for TCP/IP socket. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleSocket extends StickyError { + var $_handle; + var $_is_open; + var $_sent; + + /** + * Opens a socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @access public + */ + function SimpleSocket($host, $port, $timeout) { + $this->StickyError(); + $this->_is_open = false; + $this->_sent = ''; + if (! ($this->_handle = $this->_openSocket($host, $port, $error_number, $error, $timeout))) { + $this->_setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds"); + return; + } + $this->_is_open = true; + SimpleTestCompatibility::setTimeout($this->_handle, $timeout); + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $count = fwrite($this->_handle, $message); + if (! $count) { + if ($count === false) { + $this->_setError('Cannot write to socket'); + $this->close(); + } + return false; + } + fflush($this->_handle); + $this->_sent .= $message; + return true; + } + + /** + * Reads data from the socket. + * @param integer $block_size Size of chunk to read. + * @return integer Incoming bytes. False + * on error. + * @access public + */ + function read($block_size = 255) { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $raw = fread($this->_handle, $block_size); + if ($raw === false) { + $this->_setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->_is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + $this->_is_open = false; + return fclose($this->_handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->_sent; + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipoent of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + function _openSocket($host, $port, &$error_number, &$error, $timeout) { + return @fsockopen($host, $port, $error_number, $error, $timeout); + } + } + + /** + * Wrapper for TCP/IP socket over TLS. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleSecureSocket extends SimpleSocket { + + /** + * Opens a secure socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @access public + */ + function SimpleSecureSocket($host, $port, $timeout) { + $this->SimpleSocket($host, $port, $timeout); + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + function _openSocket($host, $port, &$error_number, &$error, $timeout) { + return parent::_openSocket("tls://$host", $port, $error_number, $error, $timeout); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/tag.php b/htdocs/TESTS/simpletest/tag.php new file mode 100644 index 0000000..159fc91 --- /dev/null +++ b/htdocs/TESTS/simpletest/tag.php @@ -0,0 +1,1155 @@ +_name = $name; + $this->_attributes = $this->_keysToLowerCase($attributes); + $this->_content = ''; + } + + /** + * Make the keys lower case for case insensitive look-ups. + * @param hash $map Hash to convert. + * @return hash Unchanged values, but keys lower case. + * @access private + */ + function _keysToLowerCase($map) { + $lower = array(); + foreach ($map as $key => $value) { + $lower[strtolower($key)] = $value; + } + return $lower; + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * Appends string content to the current content. + * @param string $content Additional text. + * @access public + */ + function addContent($content) { + $this->_content .= (string)$content; + } + + /** + * Adds an enclosed tag to the content. + * @param SimpleTag $tag New tag. + * @access public + */ + function addTag(&$tag) { + } + + /** + * Accessor for tag name. + * @return string Name of tag. + * @access public + */ + function getTagName() { + return $this->_name; + } + + /** + * List oflegal child elements. + * @return array List of element names. + * @access public + */ + function getChildElements() { + return array(); + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return string Attribute value. + * @access public + */ + function getAttribute($label) { + $label = strtolower($label); + if (! isset($this->_attributes[$label])) { + return false; + } + if ($this->_attributes[$label] === '') { + return true; + } + return (string)$this->_attributes[$label]; + } + + /** + * Sets an attribute. + * @param string $label Attribute name. + * @return string $value New attribute value. + * @access protected + */ + function _setAttribute($label, $value) { + $this->_attributes[strtolower($label)] = $value; + } + + /** + * Accessor for the whole content so far. + * @return string Content as big string. + * @access public + */ + function getContent() { + return $this->_content; + } + } + + /** + * Page title. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleTitleTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleTitleTag($attributes) { + $this->SimpleTag('title', $attributes); + } + } + + /** + * Link. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleAnchorTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleAnchorTag($attributes) { + $this->SimpleTag('a', $attributes); + } + + /** + * Accessor for URL as string. + * @return string Coerced as string. + * @access public + */ + function getHref() { + $url = $this->getAttribute('href'); + if (is_bool($url)) { + $url = ''; + } + return $url; + } + } + + /** + * Form element. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleWidget extends SimpleTag { + var $_value; + var $_is_set; + + /** + * Starts with a named tag with attributes only. + * @param string $name Tag name. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleWidget($name, $attributes) { + $this->SimpleTag($name, $attributes); + $this->_value = false; + $this->_is_set = false; + } + + /** + * Accessor for name submitted as the key in + * GET/POST variables hash. + * @return string Parsed value. + * @access public + */ + function getName() { + return $this->getAttribute('name'); + } + + /** + * Accessor for default value parsed with the tag. + * @return string Parsed value. + * @access public + */ + function getDefault() { + $default = $this->getAttribute('value'); + if ($default === true) { + $default = ''; + } + if ($default === false) { + $default = ''; + } + return $default; + } + + /** + * Accessor for currently set value or default if + * none. + * @return string Value set by form or default + * if none. + * @access public + */ + function getValue() { + if (! $this->_is_set) { + return $this->getDefault(); + } + return $this->_value; + } + + /** + * Sets the current form element value. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + $this->_value = $value; + $this->_is_set = true; + return true; + } + + /** + * Resets the form element value back to the + * default. + * @access public + */ + function resetValue() { + $this->_is_set = false; + } + } + + /** + * Text, password and hidden field. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleTextTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleTextTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', ''); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Sets the current form element value. Cannot + * change the value of a hidden field. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($this->getAttribute('type') == 'hidden') { + return false; + } + return parent::setValue($value); + } + } + + /** + * Submit button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleSubmitTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('name') === false) { + $this->_setAttribute('name', 'submit'); + } + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', 'Submit'); + } + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getValue(); + } + + /** + * Gets the values submitted as a form. + * @return array Hash of name and values. + * @access public + */ + function getSubmitValues() { + return array($this->getName() => $this->getValue()); + } + } + + /** + * Image button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleImageSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleImageSubmitTag($attributes) { + $this->SimpleWidget('input', $attributes); + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + if ($this->getAttribute('title')) { + return $this->getAttribute('title'); + } + return $this->getAttribute('alt'); + } + + /** + * Gets the values submitted as a form. + * @return array Hash of name and values. + * @access public + */ + function getSubmitValues($x, $y) { + return array( + $this->getName() . '.x' => $x, + $this->getName() . '.y' => $y); + } + } + + /** + * Submit button as button tag. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleButtonTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * Defaults are very browser dependent. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleButtonTag($attributes) { + $this->SimpleWidget('button', $attributes); + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getContent(); + } + + /** + * Gets the values submitted as a form. Gone + * for the Mozilla defaults values. + * @return array Hash of name and values. + * @access public + */ + function getSubmitValues() { + if ($this->getAttribute('name') === false) { + return array(); + } + if ($this->getAttribute('value') === false) { + return array($this->getName() => ''); + } + return array($this->getName() => $this->getValue()); + } + } + + /** + * Content tag for text area. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleTextAreaTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleTextAreaTag($attributes) { + $this->SimpleWidget('textarea', $attributes); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->_wrapIsEnabled()) { + return wordwrap( + $this->getContent(), + (integer)$this->getAttribute('cols'), + "\n"); + } + return $this->getContent(); + } + + /** + * Applies word wrapping if needed. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($this->_wrapIsEnabled()) { + $value = wordwrap( + $value, + (integer)$this->getAttribute('cols'), + "\n"); + } + return parent::setValue($value); + } + + /** + * Test to see if text should be wrapped. + * @return boolean True if wrapping on. + * @access private + */ + function _wrapIsEnabled() { + if ($this->getAttribute('cols')) { + $wrap = $this->getAttribute('wrap'); + if (($wrap == 'physical') || ($wrap == 'hard')) { + return true; + } + } + return false; + } + } + + /** + * Checkbox widget. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleCheckboxTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleCheckboxTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value in the one in the + * "value" attribute. The default for this + * attribute is "on". + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. The default + * value is "on". + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked')) { + return $this->getAttribute('value'); + } + return false; + } + } + + /** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleSelectionTag extends SimpleWidget { + var $_options; + var $_choice; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleSelectionTag($attributes) { + $this->SimpleWidget('select', $attributes); + $this->_options = array(); + $this->_choice = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag(&$tag) { + if ($tag->getTagName() == 'option') { + $this->_options[] = &$tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults. If none, then + * the first option is selected. + * @return string Selected field. + * @access public + */ + function getDefault() { + for ($i = 0; $i < count($this->_options); $i++) { + if ($this->_options[$i]->getAttribute('selected')) { + return $this->_options[$i]->getDefault(); + } + } + if (count($this->_options) > 0) { + return $this->_options[0]->getDefault(); + } + return ''; + } + + /** + * Can only set allowed values. + * @param string $value New choice. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + for ($i = 0; $i < count($this->_options); $i++) { + if ($this->_options[$i]->getContent() === $value) { + $this->_choice = $i; + return true; + } + } + return false; + } + + /** + * Accessor for current selection value. + * @return string Value attribute or + * content of opton. + * @access public + */ + function getValue() { + if ($this->_choice === false) { + return $this->getDefault(); + } + return $this->_options[$this->_choice]->getValue(); + } + } + + /** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ + class MultipleSelectionTag extends SimpleWidget { + var $_options; + var $_values; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function MultipleSelectionTag($attributes) { + $this->SimpleWidget('select', $attributes); + $this->_options = array(); + $this->_values = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag(&$tag) { + if ($tag->getTagName() == 'option') { + $this->_options[] = &$tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults to populate the + * value array(). + * @return array Selected fields. + * @access public + */ + function getDefault() { + $default = array(); + for ($i = 0; $i < count($this->_options); $i++) { + if ($this->_options[$i]->getAttribute('selected')) { + $default[] = $this->_options[$i]->getDefault(); + } + } + return $default; + } + + /** + * Can only set allowed values. + * @param array $values New choices. + * @return boolean True if allowed. + * @access public + */ + function setValue($values) { + foreach ($values as $value) { + $is_option = false; + for ($i = 0; $i < count($this->_options); $i++) { + if ($this->_options[$i]->getContent() == $value) { + $is_option = true; + break; + } + } + if (! $is_option) { + return false; + } + } + $this->_values = $values; + return true; + } + + /** + * Accessor for current selection value. + * @return array List of currently set options. + * @access public + */ + function getValue() { + if ($this->_values === false) { + return $this->getDefault(); + } + return $this->_values; + } + } + + /** + * Option for selection field. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleOptionTag extends SimpleWidget { + + /** + * Stashes the attributes. + */ + function SimpleOptionTag($attributes) { + $this->SimpleWidget('option', $attributes); + } + + /** + * Does nothing. + * @param string $value Ignored. + * @return boolean Not allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Accessor for starting value. Will be set to + * the option label if no value exists. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('value') === false) { + return $this->getContent(); + } + return $this->getAttribute('value'); + } + } + + /** + * Radio button. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleRadioButtonTag extends SimpleWidget { + + /** + * Stashes the attributes. + */ + function SimpleRadioButtonTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value in the one in the + * "value" attribute. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked')) { + return $this->getAttribute('value'); + } + return false; + } + } + + /** + * A group of tags with the same name within a form. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleCheckboxGroup { + var $_widgets; + + /** + * Starts empty. + * @access public + */ + function SimpleCheckboxGroup() { + $this->_widgets = array(); + } + + /** + * Adds a tag to the group. + * @param SimpleWidget $widget + * @access public + */ + function addWidget(&$widget) { + $this->_widgets[] = &$widget; + } + + /** + * Accessor for current selected widget or false + * if none. + * @return string/array Widget values or false if none. + * @access public + */ + function getValue() { + $values = array(); + for ($i = 0; $i < count($this->_widgets); $i++) { + if ($this->_widgets[$i]->getValue()) { + $values[] = $this->_widgets[$i]->getValue(); + } + } + return $this->_coerceValues($values); + } + + /** + * Accessor for starting value that is active. + * @return string/array Widget values or false if none. + * @access public + */ + function getDefault() { + $values = array(); + for ($i = 0; $i < count($this->_widgets); $i++) { + if ($this->_widgets[$i]->getDefault()) { + $values[] = $this->_widgets[$i]->getDefault(); + } + } + return $this->_coerceValues($values); + } + + /** + * Accessor for current set values. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean True if all values can be set. + * @access public + */ + function setValue($values) { + $values = $this->_makeArray($values); + if (! $this->_valuesArePossible($values)) { + return false; + } + for ($i = 0; $i < count($this->_widgets); $i++) { + $possible = $this->_widgets[$i]->getAttribute('value'); + if (in_array($this->_widgets[$i]->getAttribute('value'), $values)) { + $this->_widgets[$i]->setValue($possible); + } else { + $this->_widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a possible value set is legal. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean False if trying to set a + * missing value. + * @access private + */ + function _valuesArePossible($values) { + $matches = array(); + for ($i = 0; $i < count($this->_widgets); $i++) { + $possible = $this->_widgets[$i]->getAttribute('value'); + if (in_array($possible, $values)) { + $matches[] = $possible; + } + } + return ($values == $matches); + } + + /** + * Converts the output to an appropriate format. This means + * that no values is false, a single value is just that + * value and only two or more are contained in an array. + * @param array $values List of values of widgets. + * @return string/array/boolean Expected format for a tag. + * @access private + */ + function _coerceValues($values) { + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Converts false or string into array. The opposite of + * the coercian method. + * @param string/array/boolean $value A single item is converted + * to a one item list. False + * gives an empty list. + * @return array List of values, possibly empty. + * @access private + */ + function _makeArray($value) { + if ($value === false) { + return array(); + } + if (is_string($value)) { + return array($value); + } + return $value; + } + } + + /** + * A group of tags with the same name within a form. + * Used for radio buttons. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleRadioGroup { + var $_widgets; + + /** + * Starts empty. + * @access public + */ + function SimpleRadioGroup() { + $this->_widgets = array(); + } + + /** + * Adds a tag to the group. + * @param SimpleWidget $widget + * @access public + */ + function addWidget(&$widget) { + $this->_widgets[] = &$widget; + } + + /** + * Each tag is tried in turn until one is + * successfully set. The others will be + * unchecked if successful. + * @param string $value New value. + * @return boolean True if any allowed. + * @access public + */ + function setValue($value) { + if (! $this->_valueIsPossible($value)) { + return false; + } + $index = false; + for ($i = 0; $i < count($this->_widgets); $i++) { + if (! $this->_widgets[$i]->setValue($value)) { + $this->_widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a value is allowed. + * @param string Attempted value. + * @return boolean True if a valid value. + * @access private + */ + function _valueIsPossible($value) { + for ($i = 0; $i < count($this->_widgets); $i++) { + if ($this->_widgets[$i]->getAttribute('value') == $value) { + return true; + } + } + return false; + } + + /** + * Accessor for current selected widget or false + * if none. + * @return string/boolean Value attribute or + * content of opton. + * @access public + */ + function getValue() { + for ($i = 0; $i < count($this->_widgets); $i++) { + if ($this->_widgets[$i]->getValue()) { + return $this->_widgets[$i]->getValue(); + } + } + return false; + } + + /** + * Accessor for starting value that is active. + * @return string/boolean Value of first checked + * widget or false if none. + * @access public + */ + function getDefault() { + for ($i = 0; $i < count($this->_widgets); $i++) { + if ($this->_widgets[$i]->getDefault()) { + return $this->_widgets[$i]->getDefault(); + } + } + return false; + } + } + + /** + * Tag to aid parsing the form. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleFormTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleFormTag($attributes) { + $this->SimpleTag('form', $attributes); + } + } + + /** + * Tag to aid parsing the frames in a page. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleFrameTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleFrameTag($attributes) { + $this->SimpleTag('frame', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/unit_tester.php b/htdocs/TESTS/simpletest/unit_tester.php new file mode 100644 index 0000000..e918d00 --- /dev/null +++ b/htdocs/TESTS/simpletest/unit_tester.php @@ -0,0 +1,307 @@ +SimpleTestCase($label); + } + + /** + * Will be true if the value is null. + * @param null $value Supposedly null value. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNull($value, $message = "%s") { + $dumper = &new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($value) . "] should be null"); + return $this->assertTrue(! isset($value), $message); + } + + /** + * Will be true if the value is set. + * @param mixed $value Supposedly set value. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertNotNull($value, $message = "%s") { + $dumper = &new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($value) . "] should not be null"); + return $this->assertTrue(isset($value), $message); + } + + /** + * Type and class test. Will pass if class + * matches the type name or is a subclass or + * if not an object, but the type is correct. + * @param mixed $object Object to test. + * @param string $type Type name as string. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertIsA($object, $type, $message = "%s") { + return $this->assertExpectation( + new IsAExpectation($type), + $object, + $message); + } + + /** + * Type and class mismatch test. Will pass if class + * name or underling type does not match the one + * specified. + * @param mixed $object Object to test. + * @param string $type Type name as string. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertNotA($object, $type, $message = "%s") { + return $this->assertExpectation( + new NotAExpectation($type), + $object, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = "%s") { + return $this->assertExpectation( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = "%s") { + return $this->assertExpectation( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value and same type. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertIdentical($first, $second, $message = "%s") { + return $this->assertExpectation( + new IdenticalExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the different value or different type. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotIdentical($first, $second, $message = "%s") { + return $this->assertExpectation( + new NotIdenticalExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if both parameters refer + * to the same object. Fail otherwise. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertReference(&$first, &$second, $message = "%s") { + $dumper = &new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($first) . + "] and [" . $dumper->describeValue($second) . + "] should reference the same object"); + $temp = $first; + $first = uniqid("test"); + $is_ref = ($first === $second); + $first = $temp; + return $this->assertTrue($is_ref, $message); + } + + /** + * Will trigger a pass if both parameters refer + * to different objects. Fail otherwise. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully not the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertCopy(&$first, &$second, $message = "%s") { + $dumper = &new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($first) . + "] and [" . $dumper->describeValue($second) . + "] should not be the same object"); + $temp = $first; + $first = uniqid("test"); + $is_ref = ($first === $second); + $first = $temp; + return $this->assertFalse($is_ref, $message); + } + + /** + * Will trigger a pass if the Perl regex pattern + * is found in the subject. Fail otherwise. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $subject String to search in. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertWantedPattern($pattern, $subject, $message = "%s") { + return $this->assertExpectation( + new WantedPatternExpectation($pattern), + $subject, + $message); + } + + /** + * Will trigger a pass if the perl regex pattern + * is not present in subject. Fail if found. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $subject String to search in. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNoUnwantedPattern($pattern, $subject, $message = "%s") { + return $this->assertExpectation( + new UnwantedPatternExpectation($pattern), + $subject, + $message); + } + + /** + * Confirms that no errors have occoured so + * far in the test method. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNoErrors($message = "%s") { + $queue = &SimpleErrorQueue::instance(); + return $this->assertTrue( + $queue->isEmpty(), + sprintf($message, "Should be no errors")); + } + + /** + * Confirms that an error has occoured and + * optionally that the error text matches exactly. + * @param string $expected Expected error text or + * false for no check. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertError($expected = false, $message = "%s") { + $queue = &SimpleErrorQueue::instance(); + if ($queue->isEmpty()) { + $this->fail(sprintf($message, "Expected error not found")); + return; + } + list($severity, $content, $file, $line, $globals) = $queue->extract(); + $severity = SimpleErrorQueue::getSeverityAsString($severity); + return $this->assertTrue( + ! $expected || ($expected == $content), + "Expected [$expected] in PHP error [$content] severity [$severity] in [$file] line [$line]"); + } + + /** + * Confirms that an error has occoured and + * that the error text matches a Perl regular + * expression. + * @param string $expected Perl regular expresion to + * match against. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertErrorPattern($pattern, $message = "%s") { + $queue = &SimpleErrorQueue::instance(); + if ($queue->isEmpty()) { + $this->fail(sprintf($message, "Expected error not found")); + return; + } + list($severity, $content, $file, $line, $globals) = $queue->extract(); + $severity = SimpleErrorQueue::getSeverityAsString($severity); + return $this->assertTrue( + (boolean)preg_match($pattern, $content), + "Expected pattern match [$pattern] in PHP error [$content] severity [$severity] in [$file] line [$line]"); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/url.php b/htdocs/TESTS/simpletest/url.php new file mode 100644 index 0000000..d0378ed --- /dev/null +++ b/htdocs/TESTS/simpletest/url.php @@ -0,0 +1,621 @@ +_request = array(); + $this->merge($query); + } + + /** + * Adds a parameter to the query. + * @param string $key Key to add value to. + * @param string/array $value New data. + * @access public + */ + function add($key, $value) { + if (! isset($this->_request[$key])) { + $this->_request[$key] = array(); + } + if (is_array($value)) { + foreach ($value as $item) { + $this->_request[$key][] = $item; + } + } else { + $this->_request[$key][] = $value; + } + } + + /** + * Adds a set of parameters to this query. + * @param array $query/SimpleQueryString Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function merge($query) { + if (is_object($query)) { + foreach ($query->getKeys() as $key) { + $this->add($key, $query->getValue($key)); + } + } else { + foreach ($query as $key => $value) { + $this->add($key, $value); + } + } + } + + /** + * Accessor for single value. + * @return string/array False if missing, string + * if present and array if + * multiple entries. + * @access public + */ + function getValue($key) { + if (! isset($this->_request[$key])) { + return false; + } elseif (count($this->_request[$key]) == 1) { + return $this->_request[$key][0]; + } else { + return $this->_request[$key]; + } + } + + /** + * Accessor for key list. + * @return array List of keys present. + * @access public + */ + function getKeys() { + return array_keys($this->_request); + } + + /** + * Gets all parameters as structured hash. Repeated + * values are list values. + * @return array Hash of keys and value sets. + * @access public + */ + function getAll() { + $values = array(); + foreach ($this->_request as $key => $value) { + $values[$key] = (count($value) == 1 ? $value[0] : $value); + } + return $values; + } + + /** + * Renders the query string as a URL encoded + * request part. + * @return string Part of URL. + * @access public + */ + function asString() { + $statements = array(); + foreach ($this->_request as $key => $values) { + foreach ($values as $value) { + $statements[] = "$key=" . urlencode($value); + } + } + return implode('&', $statements); + } + } + + /** + * URL parser to replace parse_url() PHP function which + * got broken in PHP 4.3.0. Adds some browser specific + * functionality such as expandomatic expansion. + * Guesses a bit trying to separate the host from + * the path. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleUrl { + var $_scheme; + var $_username; + var $_password; + var $_host; + var $_port; + var $_path; + var $_request; + var $_fragment; + var $_x; + var $_y; + var $_target; + + /** + * Constructor. Parses URL into sections. + * @param string $url Incoming URL. + * @access public + */ + function SimpleUrl($url) { + list($this->_x, $this->_y) = $this->_chompCoordinates($url); + $this->_scheme = $this->_chompScheme($url); + list($this->_username, $this->_password) = $this->_chompLogin($url); + $this->_host = $this->_chompHost($url); + $this->_port = false; + if (preg_match('/(.*?):(.*)/', $this->_host, $host_parts)) { + $this->_host = $host_parts[1]; + $this->_port = (integer)$host_parts[2]; + } + $this->_path = $this->_chompPath($url); + $this->_request = $this->_parseRequest($this->_chompRequest($url)); + $this->_fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false); + $this->_target = false; + } + + /** + * Extracts the X, Y coordinate pair from an image map. + * @param string $url URL so far. The coordinates will be + * removed. + * @return array X, Y as a pair of integers. + * @access private + */ + function _chompCoordinates(&$url) { + if (preg_match('/(.*)\?(\d+),(\d+)$/', $url, $matches)) { + $url = $matches[1]; + return array((integer)$matches[2], (integer)$matches[3]); + } + return array(false, false); + } + + /** + * Extracts the scheme part of an incoming URL. + * @param string $url URL so far. The scheme will be + * removed. + * @return string Scheme part or false. + * @access private + */ + function _chompScheme(&$url) { + if (preg_match('/(.*?):(\/\/)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return false; + } + + /** + * Extracts the username and password from the + * incoming URL. The // prefix will be reattached + * to the URL after the doublet is extracted. + * @param string $url URL so far. The username and + * password are removed. + * @return array Two item list of username and + * password. Will urldecode() them. + * @access private + */ + function _chompLogin(&$url) { + $prefix = ''; + if (preg_match('/(\/\/)(.*)/', $url, $matches)) { + $prefix = $matches[1]; + $url = $matches[2]; + } + if (preg_match('/(.*?)@(.*)/', $url, $matches)) { + $url = $prefix . $matches[2]; + $parts = split(":", $matches[1]); + return array( + urldecode($parts[0]), + isset($parts[1]) ? urldecode($parts[1]) : false); + } + $url = $prefix . $url; + return array(false, false); + } + + /** + * Extracts the host part of an incoming URL. + * Includes the port number part. Will extract + * the host if it starts with // or it has + * a top level domain or it has at least two + * dots. + * @param string $url URL so far. The host will be + * removed. + * @return string Host part guess or false. + * @access private + */ + function _chompHost(&$url) { + if (preg_match('/(\/\/)(.*?)(\/.*|\?.*|#.*|$)/', $url, $matches)) { + $url = $matches[3]; + return $matches[2]; + } + if (preg_match('/(.*?)(\.\.\/|\.\/|\/|\?|#|$)(.*)/', $url, $matches)) { + if (preg_match('/[a-z0-9\-]+\.(com|edu|net|org|gov|mil|int)/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } elseif (preg_match('/[a-z0-9\-]+\.[a-z0-9\-]+\.[a-z0-9\-]+/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + } + return false; + } + + /** + * Extracts the path information from the incoming + * URL. Strips this path from the URL. + * @param string $url URL so far. The host will be + * removed. + * @return string Path part or '/'. + * @access private + */ + function _chompPath(&$url) { + if (preg_match('/(.*?)(\?|#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return ($matches[1] ? $matches[1] : ''); + } + return ''; + } + + /** + * Strips off the request data. + * @param string $url URL so far. The request will be + * removed. + * @return string Raw request part. + * @access private + */ + function _chompRequest(&$url) { + if (preg_match('/\?(.*?)(#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return ''; + } + + /** + * Breaks the request down into an object. + * @param string $raw Raw request. + * @return SimpleQueryString Parsed data. + * @access private + */ + function _parseRequest($raw) { + $request = new SimpleQueryString(); + foreach (split("&", $raw) as $pair) { + if (preg_match('/(.*?)=(.*)/', $pair, $matches)) { + $request->add($matches[1], urldecode($matches[2])); + } elseif ($pair) { + $request->add($pair, ''); + } + } + return $request; + } + + /** + * Accessor for protocol part. + * @param string $default Value to use if not present. + * @return string Scheme name, e.g "http". + * @access public + */ + function getScheme($default = false) { + return $this->_scheme ? $this->_scheme : $default; + } + + /** + * Accessor for user name. + * @return string Username preceding host. + * @access public + */ + function getUsername() { + return $this->_username; + } + + /** + * Accessor for password. + * @return string Password preceding host. + * @access public + */ + function getPassword() { + return $this->_password; + } + + /** + * Accessor for hostname and port. + * @param string $default Value to use if not present. + * @return string Hostname only. + * @access public + */ + function getHost($default = false) { + return $this->_host ? $this->_host : $default; + } + + /** + * Accessor for top level domain. + * @return string Last part of host. + * @access public + */ + function getTld() { + $path_parts = pathinfo($this->getHost()); + return (isset($path_parts['extension']) ? $path_parts['extension'] : false); + } + + /** + * Accessor for port number. + * @return integer TCP/IP port number. + * @access public + */ + function getPort() { + return $this->_port; + } + + /** + * Accessor for path. + * @return string Full path including leading slash if implied. + * @access public + */ + function getPath() { + if (! $this->_path && $this->_host) { + return '/'; + } + return $this->_path; + } + + /** + * Accessor for page if any. This may be a + * directory name if ambiguious. + * @return Page name. + * @access public + */ + function getPage() { + if (! preg_match('/([^\/]*?)$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Gets the path to the page. + * @return string Path less the page. + * @access public + */ + function getBasePath() { + if (! preg_match('/(.*\/)[^\/]*?$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Accessor for fragment at end of URL after the "#". + * @return string Part after "#". + * @access public + */ + function getFragment() { + return $this->_fragment; + } + + /** + * Accessor for horizontal image coordinate. + * @return integer X value. + * @access public + */ + function getX() { + return $this->_x; + } + + /** + * Accessor for vertical image coordinate. + * @return integer Y value. + * @access public + */ + function getY() { + return $this->_y; + } + + /** + * Accessor for current request parameters + * in URL string form + * @return string Form is string "?a=1&b=2", etc. + * @access public + */ + function getEncodedRequest() { + $query = $this->_request; + $encoded = $query->asString(); + if ($encoded) { + return "?$encoded"; + } + return ''; + } + + /** + * Encodes parameters as HTTP request parameters. + * @param hash $parameters Request as hash. + * @return string Encoded request. + * @access public + * @static + */ + function encodeRequest($parameters) { + if (! $parameters) { + return ''; + } + $query = &new SimpleQueryString(); + foreach ($parameters as $key => $value) { + $query->add($key, $value); + } + return $query->asString(); + } + + /** + * Accessor for current request parameters + * as an object. + * @return array Hash of name and value pairs. The + * values will be lists for repeated items. + * @access public + */ + function getRequest() { + return $this->_request->getAll(); + } + + /** + * Adds an additional parameter to the request. + * @param string $key Name of parameter. + * @param string $value Value as string. + * @access public + */ + function addRequestParameter($key, $value) { + $this->_request->add($key, $value); + } + + /** + * Adds additional parameters to the request. + * @param hash $parameters Hash of additional parameters. + * @access public + */ + function addRequestParameters($parameters) { + if ($parameters) { + $this->_request->merge($parameters); + } + } + + /** + * Clears down all parameters. + * @access public + */ + function clearRequest() { + $this->_request = &new SimpleQueryString(); + } + + /** + * Sets image coordinates. Set to flase to clear + * them. + * @param integer $x Horizontal position. + * @param integer $y Vertical position. + * @access public + */ + function setCoordinates($x = false, $y = false) { + if (($x === false) || ($y === false)) { + $this->_x = $this->_y = false; + return; + } + $this->_x = (integer)$x; + $this->_y = (integer)$y; + } + + /** + * Gets the frame target if present. Although + * not strictly part of the URL specification it + * acts as similarily to the browser. + * @return boolean/string Frame name or false if none. + * @access public + */ + function getTarget() { + return $this->_target; + } + + /** + * Attaches a frame target. + * @param string $frame Name of frame. + * @access public + */ + function setTarget($frame) { + $this->_target = $frame; + } + + /** + * Renders the URL back into a string. + * @return string URL in canonical form. + * @access public + */ + function asString() { + $scheme = $identity = $host = $path = $encoded = $fragment = ''; + if ($this->_username && $this->_password) { + $identity = $this->_username . ':' . $this->_password . '@'; + } + if ($this->getHost()) { + $scheme = $this->getScheme() ? $this->getScheme() : 'http'; + $host = $this->getHost(); + } + if (substr($this->_path, 0, 1) == '/') { + $path = $this->normalisePath($this->_path); + } + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = ($this->_x !== false) ? '?' . $this->_x . ',' . $this->_y : ''; + return "$scheme://$identity$host$path$encoded$fragment$coords"; + } + + /** + * Replaces unknown sections to turn a relative + * URL into an absolute one. The base URL can + * be either a string or a SimpleUrl object. + * @param string/SimpleUrl $base Base URL. + * @access public + */ + function makeAbsolute($base) { + if (! is_object($base)) { + $base = new SimpleUrl($base); + } + $scheme = $this->getScheme() ? $this->getScheme() : $base->getScheme(); + $host = $this->getHost(); + $port = $this->getPort() ? ':' . $this->getPort() : ''; + $path = $this->normalisePath($this->_path); + if (! $host) { + $host = $base->getHost(); + $port = $base->getPort() ? ':' . $base->getPort() : ''; + if ($this->_isRelativePath($this->_path)) { + $path = $this->normalisePath($base->getBasePath() . $this->_path); + } + } + $identity = $this->_getIdentity() ? $this->_getIdentity() . '@' : ''; + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = ($this->_x !== false) ? '?' . $this->_x . ',' . $this->_y : ''; + return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords"); + } + + /** + * Simple test to see if a path part is relative. + * @param string $path Path to test. + * @return boolean True if starts with a "/". + * @access private + */ + function _isRelativePath($path) { + return (substr($path, 0, 1) != '/'); + } + + /** + * Extracts the username and password for use in rendering + * a URL. + * @return string/boolean Form of username:password@ or false. + * @access private + */ + function _getIdentity() { + if ($this->_username && $this->_password) { + return $this->_username . ':' . $this->_password; + } + return false; + } + + /** + * Replaces . and .. sections of the path. + * @param string $path Unoptimised path. + * @return string Path with dots removed if possible. + * @access public + */ + function normalisePath($path) { + $path = preg_replace('|/[^/]+/\.\./|', '/', $path); + return preg_replace('|/\./|', '/', $path); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/user_agent.php b/htdocs/TESTS/simpletest/user_agent.php new file mode 100644 index 0000000..a2b7a9d --- /dev/null +++ b/htdocs/TESTS/simpletest/user_agent.php @@ -0,0 +1,486 @@ +_cookies = array(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $now Time to test expiry against. + * @access public + */ + function restartSession($date = false) { + $surviving_cookies = array(); + for ($i = 0; $i < count($this->_cookies); $i++) { + if (! $this->_cookies[$i]->getValue()) { + continue; + } + if (! $this->_cookies[$i]->getExpiry()) { + continue; + } + if ($date && $this->_cookies[$i]->isExpired($date)) { + continue; + } + $surviving_cookies[] = $this->_cookies[$i]; + } + $this->_cookies = $surviving_cookies; + } + + /** + * Ages all cookies in the cookie jar. + * @param integer $interval The old session is moved + * into the past by this number + * of seconds. Cookies now over + * age will be removed. + * @access public + */ + function agePrematurely($interval) { + for ($i = 0; $i < count($this->_cookies); $i++) { + $this->_cookies[$i]->agePrematurely($interval); + } + } + + /** + * Adds a cookie to the jar. This will overwrite + * cookies with matching host, paths and keys. + * @param SimpleCookie $cookie New cookie. + * @access public + */ + function setCookie($cookie) { + for ($i = 0; $i < count($this->_cookies); $i++) { + $is_match = $this->_isMatch( + $cookie, + $this->_cookies[$i]->getHost(), + $this->_cookies[$i]->getPath(), + $this->_cookies[$i]->getName()); + if ($is_match) { + $this->_cookies[$i] = $cookie; + return; + } + } + $this->_cookies[] = $cookie; + } + + /** + * Fetches a hash of all valid cookies filtered + * by host, path and keyed by name + * Any cookies with missing categories will not + * be filtered out by that category. Expired + * cookies must be cleared by restarting the session. + * @param string $host Host name requirement. + * @param string $path Path encompassing cookies. + * @return hash Valid cookie objects keyed + * on the cookie name. + * @access public + */ + function getValidCookies($host = false, $path = "/") { + $valid_cookies = array(); + foreach ($this->_cookies as $cookie) { + if ($this->_isMatch($cookie, $host, $path, $cookie->getName())) { + $valid_cookies[] = $cookie; + } + } + return $valid_cookies; + } + + /** + * Tests cookie for matching against search + * criteria. + * @param SimpleTest $cookie Cookie to test. + * @param string $host Host must match. + * @param string $path Cookie path must be shorter than + * this path. + * @param string $name Name must match. + * @return boolean True if matched. + * @access private + */ + function _isMatch($cookie, $host, $path, $name) { + if ($cookie->getName() != $name) { + return false; + } + if ($host && $cookie->getHost() && !$cookie->isValidHost($host)) { + return false; + } + if (! $cookie->isValidPath($path)) { + return false; + } + return true; + } + + /** + * Adds the current cookies to a request. + * @param SimpleHttpRequest $request Request to modify. + * @param SimpleUrl $url Cookie selector. + * @access private + */ + function addHeaders(&$request, $url) { + $cookies = $this->getValidCookies($url->getHost(), $url->getPath()); + foreach ($cookies as $cookie) { + $request->setCookie($cookie); + } + } + } + + /** + * Fetches web pages whilst keeping track of + * cookies and authentication. + * @package SimpleTest + * @subpackage WebTester + */ + class SimpleUserAgent { + var $_cookie_jar; + var $_authenticator; + var $_max_redirects; + var $_proxy; + var $_proxy_username; + var $_proxy_password; + var $_connection_timeout; + var $_additional_headers; + + /** + * Starts with no cookies, realms or proxies. + * @access public + */ + function SimpleUserAgent() { + $this->_cookie_jar = &new SimpleCookieJar(); + $this->_authenticator = &new SimpleAuthenticator(); + $this->setMaximumRedirects(DEFAULT_MAX_REDIRECTS); + $this->_proxy = false; + $this->_proxy_username = false; + $this->_proxy_password = false; + $this->setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT); + $this->_additional_headers = array(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $date Time when session restarted. + * If omitted then all persistent + * cookies are kept. + * @access public + */ + function restartSession($date = false) { + $this->_cookie_jar->restartSession($date); + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->_additional_headers[] = $header; + } + + /** + * Ages the cookies by the specified time. + * @param integer $interval Amount in seconds. + * @access public + */ + function ageCookies($interval) { + $this->_cookie_jar->agePrematurely($interval); + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $cookie = new SimpleCookie($name, $value, $path, $expiry); + if ($host) { + $cookie->setHost($host); + } + $this->_cookie_jar->setCookie($cookie); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + $longest_path = ''; + foreach ($this->_cookie_jar->getValidCookies($host, $path) as $cookie) { + if ($name == $cookie->getName()) { + if (strlen($cookie->getPath()) > strlen($longest_path)) { + $value = $cookie->getValue(); + $longest_path = $cookie->getPath(); + } + } + } + return (isset($value) ? $value : false); + } + + /** + * Reads the current cookies within the base URL. + * @param string $name Key of cookie to find. + * @param SimpleUrl $base Base URL to search from. + * @return string Null if there is no base URL, false + * if the cookie is not set. + * @access public + */ + function getBaseCookieValue($name, $base) { + if (! $base) { + return null; + } + return $this->getCookieValue($base->getHost(), $base->getPath(), $name); + } + + /** + * Sets the socket timeout for opening a connection. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->_connection_timeout = $timeout; + } + + /** + * Sets the maximum number of redirects before + * a page will be loaded anyway. + * @param integer $max Most hops allowed. + * @access public + */ + function setMaximumRedirects($max) { + $this->_max_redirects = $max; + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username, $password) { + if (! $proxy) { + $this->_proxy = false; + return; + } + if (strncmp($proxy, 'http://', 7) != 0) { + $proxy = 'http://'. $proxy; + } + $this->_proxy = &new SimpleUrl($proxy); + $this->_proxy_username = $username; + $this->_proxy_password = $password; + } + + /** + * Test to see if the redirect limit is passed. + * @param integer $redirects Count so far. + * @return boolean True if over. + * @access private + */ + function _isTooManyRedirects($redirects) { + return ($redirects > $this->_max_redirects); + } + + /** + * Sets the identity for the current realm. + * @param string $host Host to which realm applies. + * @param string $realm Full name of realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + function setIdentity($host, $realm, $username, $password) { + $this->_authenticator->setIdentityForRealm($host, $realm, $username, $password); + } + + /** + * Fetches a URL as a response object. Will keep trying if redirected. + * It will also collect authentication realm information. + * @param string $method GET, POST, etc. + * @param string/SimpleUrl $url Target to fetch. + * @param hash $parameters Additional parameters for request. + * @return SimpleHttpResponse Hopefully the target page. + * @access public + */ + function &fetchResponse($method, $url, $parameters = false) { + if ($method != 'POST') { + $url->addRequestParameters($parameters); + $parameters = false; + } + $response = &$this->_fetchWhileRedirected($method, $url, $parameters); + if ($headers = $response->getHeaders()) { + if ($headers->isChallenge()) { + $this->_authenticator->addRealm( + $url, + $headers->getAuthentication(), + $headers->getRealm()); + } + } + return $response; + } + + /** + * Fetches the page until no longer redirected or + * until the redirect limit runs out. + * @param string $method GET, POST, etc. + * @param SimpleUrl $url Target to fetch. + * @param hash $parameters Additional parameters for request. + * @return SimpleHttpResponse Hopefully the target page. + * @access private + */ + function &_fetchWhileRedirected($method, $url, $parameters) { + $redirects = 0; + do { + $response = &$this->_fetch($method, $url, $parameters); + if ($response->isError()) { + return $response; + } + $headers = $response->getHeaders(); + $location = new SimpleUrl($headers->getLocation()); + $url = $location->makeAbsolute($url); + $this->_addCookiesToJar($url, $headers->getNewCookies()); + if (! $headers->isRedirect()) { + break; + } + $method = 'GET'; + $parameters = false; + } while (! $this->_isTooManyRedirects(++$redirects)); + return $response; + } + + /** + * Actually make the web request. + * @param string $method GET, POST, etc. + * @param SimpleUrl $url Target to fetch. + * @param hash $parameters Additional parameters for request. + * @return SimpleHttpResponse Headers and hopefully content. + * @access protected + */ + function &_fetch($method, $url, $parameters) { + if (! $parameters) { + $parameters = array(); + } + $request = &$this->_createRequest($method, $url, $parameters); + return $request->fetch($this->_connection_timeout); + } + + /** + * Creates a full page request. + * @param string $method Fetching method. + * @param SimpleUrl $url Target to fetch as url object. + * @param hash $parameters POST/GET parameters. + * @return SimpleHttpRequest New request. + * @access private + */ + function &_createRequest($method, $url, $parameters) { + $request = &$this->_createHttpRequest($method, $url, $parameters); + $this->_addAdditionalHeaders($request); + $this->_cookie_jar->addHeaders($request, $url); + $this->_authenticator->addHeaders($request, $url); + return $request; + } + + /** + * Builds the appropriate HTTP request object. + * @param string $method Fetching method. + * @param SimpleUrl $url Target to fetch as url object. + * @param hash $parameters POST/GET parameters. + * @return SimpleHttpRequest New request object. + * @access protected + */ + function &_createHttpRequest($method, $url, $parameters) { + if ($method == 'POST') { + $request = &new SimpleHttpPostRequest( + $this->_createRoute($url), + $parameters); + return $request; + } + if ($parameters) { + $url->addRequestParameters($parameters); + } + return new SimpleHttpRequest($this->_createRoute($url), $method); + } + + /** + * Sets up either a direct route or via a proxy. + * @param SimpleUrl $url Target to fetch as url object. + * @return SimpleRoute Route to take to fetch URL. + * @access protected + */ + function &_createRoute($url) { + if ($this->_proxy) { + return new SimpleProxyRoute( + $url, + $this->_proxy, + $this->_proxy_username, + $this->_proxy_password); + } + return new SimpleRoute($url); + } + + /** + * Adds additional manual headers. + * @param SimpleHttpRequest $request Outgoing request. + * @access private + */ + function _addAdditionalHeaders(&$request) { + foreach ($this->_additional_headers as $header) { + $request->addHeaderLine($header); + } + } + + /** + * Extracts new cookies into the cookie jar. + * @param SimpleUrl $url Target to fetch as url object. + * @param array $cookies New cookies. + * @access private + */ + function _addCookiesToJar($url, $cookies) { + foreach ($cookies as $cookie) { + if ($url->getHost()) { + $cookie->setHost($url->getHost()); + } + $this->_cookie_jar->setCookie($cookie); + } + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/web_tester.php b/htdocs/TESTS/simpletest/web_tester.php new file mode 100644 index 0000000..baecfa3 --- /dev/null +++ b/htdocs/TESTS/simpletest/web_tester.php @@ -0,0 +1,1099 @@ +SimpleExpectation(); + if (is_array($value)) { + sort($value); + } + $this->_value = $value; + } + + /** + * Tests the expectation. True if it matches + * a string value or an array value in any order. + * @param mixed $compare Comparison value. False for + * an unset field. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + if ($this->_value === false) { + return ($compare === false); + } + if ($this->_isSingle($this->_value)) { + return $this->_testSingle($compare); + } + if (is_array($this->_value)) { + return $this->_testMultiple($compare); + } + return false; + } + + /** + * Tests for valid field comparisons. + * @param mixed $value Value to type check. + * @return boolean True if integer, string or float. + * @access private + */ + function _isSingle($value) { + return is_string($value) || is_integer($value) || is_float($value); + } + + /** + * String comparison for simple field. + * @param mixed $compare String to test against. + * @returns boolean True if matching. + * @access private + */ + function _testSingle($compare) { + if (is_array($compare) && count($compare) == 1) { + $compare = $compare[0]; + } + if (! $this->_isSingle($compare)) { + return false; + } + return ($this->_value == $compare); + } + + /** + * List comparison for multivalue field. + * @param mixed $compare List in any order to test against. + * @returns boolean True if matching. + * @access private + */ + function _testMultiple($compare) { + if (is_string($compare)) { + $compare = array($compare); + } + if (! is_array($compare)) { + return false; + } + sort($compare); + return ($this->_value === $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + if (is_array($compare)) { + sort($compare); + } + if ($this->test($compare)) { + return "Field expectation [" . $dumper->describeValue($this->_value) . "]"; + } else { + return "Field expectation [" . $dumper->describeValue($this->_value) . + "] fails with [" . + $this->_dumper->describeValue($compare) . "] " . + $this->_dumper->describeDifference($this->_value, $compare); + } + } + } + + /** + * Test for a specific HTTP header within a header block. + * @package SimpleTest + * @subpackage WebTester + */ + class HttpHeaderExpectation extends SimpleExpectation { + var $_expected_header; + var $_expected_value; + + /** + * Sets the field and value to compare against. + * @param string $header Case insenstive trimmed header name. + * @param string $value Optional value to compare. If not + * given then any value will match. + */ + function HttpHeaderExpectation($header, $value = false) { + $this->_expected_header = $this->_normaliseHeader($header); + $this->_expected_value = $value; + } + + /** + * Accessor for subclases. + * @return mixed Expectation set in constructor. + * @access protected + */ + function _getExpectation() { + return $this->_expected_value; + } + + /** + * Removes whitespace at ends and case variations. + * @param string $header Name of header. + * @param string Trimmed and lowecased header + * name. + * @access private + */ + function _normaliseHeader($header) { + return strtolower(trim($header)); + } + + /** + * Tests the expectation. True if it matches + * a string value or an array value in any order. + * @param mixed $compare Raw header block to search. + * @return boolean True if header present. + * @access public + */ + function test($compare) { + return is_string($this->_findHeader($compare)); + } + + /** + * Searches the incoming result. Will extract the matching + * line as text. + * @param mixed $compare Raw header block to search. + * @return string Matching header line. + * @access protected + */ + function _findHeader($compare) { + $lines = split("\r\n", $compare); + foreach ($lines as $line) { + if ($this->_testHeaderLine($line)) { + return $line; + } + } + return false; + } + + /** + * Compares a single header line against the expectation. + * @param string $line A single line to compare. + * @return boolean True if matched. + * @access private + */ + function _testHeaderLine($line) { + if (count($parsed = split(':', $line)) < 2) { + return false; + } + list($header, $value) = $parsed; + if ($this->_normaliseHeader($header) != $this->_expected_header) { + return false; + } + return $this->_testHeaderValue($value, $this->_expected_value); + } + + /** + * Tests the value part of the header. + * @param string $value Value to test. + * @param mixed $expected Value to test against. + * @return boolean True if matched. + * @access protected + */ + function _testHeaderValue($value, $expected) { + if ($expected === false) { + return true; + } + return (trim($value) == trim($expected)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Raw header block to search. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $expectation = $this->_expected_header; + if ($this->_expected_value) { + $expectation .= ': ' . $this->_expected_header; + } + if (is_string($line = $this->_findHeader($compare))) { + return "Searching for header [$expectation] found [$line]"; + } else { + return "Failed to find header [$expectation]"; + } + } + } + + /** + * Test for a specific HTTP header within a header block that + * should not be found. + * @package SimpleTest + * @subpackage WebTester + */ + class HttpUnwantedHeaderExpectation extends HttpHeaderExpectation { + var $_expected_header; + var $_expected_value; + + /** + * Sets the field and value to compare against. + * @param string $unwanted Case insenstive trimmed header name. + */ + function HttpUnwantedHeaderExpectation($unwanted) { + $this->HttpHeaderExpectation($unwanted); + } + + /** + * Tests that the unwanted header is not found. + * @param mixed $compare Raw header block to search. + * @return boolean True if header present. + * @access public + */ + function test($compare) { + return ($this->_findHeader($compare) === false); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Raw header block to search. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $expectation = $this->_getExpectation(); + if (is_string($line = $this->_findHeader($compare))) { + return "Found unwanted header [$expectation] with [$line]"; + } else { + return "Did not find unwanted header [$expectation]"; + } + } + } + + /** + * Test for a specific HTTP header within a header block. + * @package SimpleTest + * @subpackage WebTester + */ + class HttpHeaderPatternExpectation extends HttpHeaderExpectation { + + /** + * Sets the field and value to compare against. + * @param string $header Case insenstive header name. + * @param string $pattern Pattern to compare value against. + * @access public + */ + function HttpHeaderPatternExpectation($header, $pattern) { + $this->HttpHeaderExpectation($header, $pattern); + } + + /** + * Tests the value part of the header. + * @param string $value Value to test. + * @param mixed $pattern Pattern to test against. + * @return boolean True if matched. + * @access protected + */ + function _testHeaderValue($value, $expected) { + return (boolean)preg_match($expected, trim($value)); + } + } + + /** + * Test case for testing of web pages. Allows + * fetching of pages, parsing of HTML and + * submitting forms. + * @package SimpleTest + * @subpackage WebTester + */ + class WebTestCase extends SimpleTestCase { + var $_browser; + + /** + * Creates an empty test case. Should be subclassed + * with test methods for a functional test case. + * @param string $label Name of test case. Will use + * the class name if none specified. + * @access public + */ + function WebTestCase($label = false) { + $this->SimpleTestCase($label); + } + + /** + * Gets the last response error. + * @return string Last low level HTTP error. + * @access public + */ + function getTransportError() { + return $this->_browser->getTransportError(); + } + + /** + * Accessor for the currently selected URL. + * @return string Current location or false if + * no page yet fetched. + * @access public + */ + function getUrl() { + $this->_browser->getUrl(); + } + + /** + * Dumps the current request for debugging. + * @access public + */ + function showRequest() { + $this->dump($this->_browser->getRequest()); + } + + /** + * Dumps the current HTTP headers for debugging. + * @access public + */ + function showHeaders() { + $this->dump($this->_browser->getHeaders()); + } + + /** + * Dumps the current HTML source for debugging. + * @access public + */ + function showSource() { + $this->dump($this->_browser->getContent()); + } + + /** + * Simulates the closing and reopening of the browser. + * Temporary cookies will be discarded and timed + * cookies will be expired if later than the + * specified time. + * @param string/integer $date Time when session restarted. + * If ommitted then all persistent + * cookies are kept. Time is either + * Cookie format string or timestamp. + * @access public + */ + function restartSession($date = false) { + if ($date === false) { + $date = time(); + } + $this->_browser->restartSession($date); + } + + /** + * Moves cookie expiry times back into the past. + * Useful for testing timeouts and expiries. + * @param integer $interval Amount to age in seconds. + * @access public + */ + function ageCookies($interval) { + $this->_browser->ageCookies($interval); + } + + /** + * Gets a current browser reference for setting + * special expectations or for detailed + * examination of page fetches. + * @param SimpleBrowser $browser Test browser object. + * @access public + */ + function &getBrowser() { + return $this->_browser; + } + + /** + * Creates a new default web browser object. + * Will be cleared at the end of the test method. + * @return TestBrowser New browser. + * @access public + */ + function &createBrowser() { + return new SimpleBrowser(); + } + + /** + * Sets up a browser for the start of each + * test method. + * @access public + */ + function before() { + $this->_browser = &$this->createBrowser(); + parent::before(); + } + + /** + * Disables frames support. Frames will not be fetched + * and the frameset page will be used instead. + * @access public + */ + function ignoreFrames() { + $this->_browser->ignoreFrames(); + } + + /** + * Sets a cookie in the current browser. + * @param string $name Name of cookie. + * @param string $value Cookie value. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = "/", $expiry = false) { + $this->_browser->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->_browser->addHeader($header); + } + + /** + * Sets the maximum number of redirects before + * the web page is loaded regardless. + * @param integer $max Maximum hops. + * @access public + */ + function setMaximumRedirects($max) { + if (! $this->_browser) { + trigger_error( + 'Can only set maximum redirects in a test method, setUp() or tearDown()'); + } + $this->_browser->setMaximumRedirects($max); + } + + /** + * Sets the socket timeout for opening a connection and + * receiving at least one byte of information. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->_browser->setConnectionTimeout($timeout); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username = false, $password = false) { + $this->_browser->useProxy($proxy, $username, $password); + } + + /** + * Fetches a page into the page buffer. If + * there is no base for the URL then the + * current base URL is used. After the fetch + * the base URL reflects the new location. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean True on success. + * @access public + */ + function get($url, $parameters = false) { + $content = $this->_browser->get($url, $parameters); + if ($content === false) { + return false; + } + return true; + } + + /** + * Fetches a page by POST into the page buffer. + * If there is no base for the URL then the + * current base URL is used. After the fetch + * the base URL reflects the new location. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean True on success. + * @access public + */ + function post($url, $parameters = false) { + $content = $this->_browser->post($url, $parameters); + if ($content === false) { + return false; + } + return true; + } + + /** + * Does a HTTP HEAD fetch, fetching only the page + * headers. The current base URL is unchanged by this. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean True on success. + * @access public + */ + function head($url, $parameters = false) { + return $this->_browser->head($url, $parameters); + } + + /** + * Equivalent to hitting the retry button on the + * browser. Will attempt to repeat the page fetch. + * @return boolean True if fetch succeeded. + * @access public + */ + function retry() { + return $this->_browser->retry(); + } + + /** + * Equivalent to hitting the back button on the + * browser. + * @return boolean True if history entry and + * fetch succeeded. + * @access public + */ + function back() { + return $this->_browser->back(); + } + + /** + * Equivalent to hitting the forward button on the + * browser. + * @return boolean True if history entry and + * fetch succeeded. + * @access public + */ + function forward() { + return $this->_browser->forward(); + } + + /** + * Retries a request after setting the authentication + * for the current realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @return boolean True if successful fetch. Note + * that authentication may still have + * failed. + * @access public + */ + function authenticate($username, $password) { + return $this->_browser->authenticate($username, $password); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. + * @return integer/string/boolean Label if any, otherwise + * the position in the frameset + * or false if none. + * @access public + */ + function getFrameFocus() { + return $this->_browser->getFrameFocus(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + return $this->_browser->setFrameFocusByIndex($choice); + } + + /** + * Sets the focus by name. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + return $this->_browser->setFrameFocus($name); + } + + /** + * Clears the frame focus. All frames will be searched + * for content. + * @access public + */ + function clearFrameFocus() { + return $this->_browser->clearFrameFocus(); + } + + /** + * Clicks the submit button by label. The owning + * form will be submitted by this. + * @param string $label Button label. An unlabeled + * button can be triggered by 'Submit'. + * @return boolean True on success. + * @access public + */ + function clickSubmit($label = 'Submit') { + return $this->_browser->clickSubmit($label); + } + + /** + * Clicks the submit button by name attribute. The owning + * form will be submitted by this. + * @param string $name Name attribute of button. + * @return boolean True on success. + * @access public + */ + function clickSubmitByName($name) { + return $this->_browser->clickSubmitByName($name); + } + + /** + * Clicks the submit button by ID attribute. The owning + * form will be submitted by this. + * @param string $id ID attribute of button. + * @return boolean True on successful submit. + * @access public + */ + function clickSubmitById($id) { + return $this->_browser->clickSubmitById($id); + } + + /** + * Clicks the submit image by some kind of label. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $label Alt attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @return boolean True on successful submit. + * @access public + */ + function clickImage($label, $x = 1, $y = 1) { + return $this->_browser->clickImage($label, $x, $y); + } + + /** + * Clicks the submit image by the name. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $name Name attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @return boolean True on successful submit. + * @access public + */ + function clickImageByName($name, $x = 1, $y = 1) { + return $this->_browser->clickImageByName($name, $x, $y); + } + + /** + * Clicks the submit image by ID attribute. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param integer/string $id ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @return boolean True on successful submit. + * @access public + */ + function clickImageById($id, $x = 1, $y = 1) { + return $this->_browser->clickImageById($id, $x, $y); + } + + /** + * Submits a form by the ID. + * @param string $id Form ID. No button information + * is submitted this way. + * @return boolean True on success. + * @access public + */ + function submitFormById($id) { + return $this->_browser->submitFormById($id); + } + + /** + * Follows a link by name. Will click the first link + * found with this link text by default, or a later + * one if an index is given. Match is case insensitive + * with normalised space. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return boolean True if link present. + * @access public + */ + function clickLink($label, $index = 0) { + return $this->_browser->clickLink($label, $index); + } + + /** + * Follows a link by id attribute. + * @param string $id ID attribute value. + * @return boolean True if successful. + * @access public + */ + function clickLinkById($id) { + return $this->_browser->clickLinkById($id); + } + + /** + * Tests for the presence of a link label. Match is + * case insensitive with normalised space. + * @param string $label Text between the anchor tags. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link present. + * @access public + */ + function assertLink($label, $message = "%s") { + return $this->assertTrue( + $this->_browser->isLink($label), + sprintf($message, "Link [$label] should exist")); + } + + /** + * Tests for the non-presence of a link label. Match is + * case insensitive with normalised space. + * @param string/integer $label Text between the anchor tags + * or ID attribute. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link missing. + * @access public + */ + function assertNoLink($label, $message = "%s") { + return $this->assertFalse( + $this->_browser->isLink($label) || $this->_browser->isLinkById($label), + sprintf($message, "Link [$label] should not exist")); + } + + /** + * Tests for the presence of a link id attribute. + * @param string $id Id attribute value. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link present. + * @access public + */ + function assertLinkById($id, $message = "%s") { + return $this->assertTrue( + $this->_browser->isLinkById($id), + sprintf($message, "Link ID [$id] should exist")); + } + + /** + * Sets all form fields with that name. + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setField($name, $value) { + return $this->_browser->setField($name, $value); + } + + /** + * Sets all form fields with that name. + * @param string/integer $id Id of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldById($id, $value) { + return $this->_browser->setFieldById($id, $value); + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form will always + * fail. If no value is given then only the existence + * of the field is checked. + * @param string $name Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertField($name, $expected = true, $message = "%s") { + $value = $this->_browser->getField($name); + if ($expected === true) { + return $this->assertTrue( + isset($value), + sprintf($message, "Field [$name] should exist")); + } else { + return $this->assertExpectation( + new FieldExpectation($expected), + $value, + sprintf($message, "Field [$name] should match with [%s]")); + } + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form will always + * fail. If no ID is given then only the existence + * of the field is checked. + * @param string/integer $id Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertFieldById($id, $expected = true, $message = "%s") { + $value = $this->_browser->getFieldById($id); + if ($expected === true) { + return $this->assertTrue( + isset($value), + sprintf($message, "Field of ID [$id] should exist")); + } else { + return $this->assertExpectation( + new FieldExpectation($expected), + $value, + sprintf($message, "Field of ID [$id] should match with [%s]")); + } + } + + /** + * Checks the response code against a list + * of possible values. + * @param array $responses Possible responses for a pass. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertResponse($responses, $message = '%s') { + $responses = (is_array($responses) ? $responses : array($responses)); + $code = $this->_browser->getResponseCode(); + $message = sprintf($message, "Expecting response in [" . + implode(", ", $responses) . "] got [$code]"); + return $this->assertTrue(in_array($code, $responses), $message); + } + + /** + * Checks the mime type against a list + * of possible values. + * @param array $types Possible mime types for a pass. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertMime($types, $message = '%s') { + $types = (is_array($types) ? $types : array($types)); + $type = $this->_browser->getMimeType(); + $message = sprintf($message, "Expecting mime type in [" . + implode(", ", $types) . "] got [$type]"); + return $this->assertTrue(in_array($type, $types), $message); + } + + /** + * Attempt to match the authentication type within + * the security realm we are currently matching. + * @param string $authentication Usually basic. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertAuthentication($authentication = false, $message = '%s') { + if (! $authentication) { + $message = sprintf($message, "Expected any authentication type, got [" . + $this->_browser->getAuthentication() . "]"); + return $this->assertTrue( + $this->_browser->getAuthentication(), + $message); + } else { + $message = sprintf($message, "Expected authentication [$authentication] got [" . + $this->_browser->getAuthentication() . "]"); + return $this->assertTrue( + strtolower($this->_browser->getAuthentication()) == strtolower($authentication), + $message); + } + } + + /** + * Checks that no authentication is necessary to view + * the desired page. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoAuthentication($message = '%s') { + $message = sprintf($message, "Expected no authentication type, got [" . + $this->_browser->getAuthentication() . "]"); + return $this->assertFalse($this->_browser->getAuthentication(), $message); + } + + /** + * Attempts to match the current security realm. + * @param string $realm Name of security realm. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertRealm($realm, $message = '%s') { + $message = sprintf($message, "Expected realm [$realm] got [" . + $this->_browser->getRealm() . "]"); + return $this->assertTrue( + strtolower($this->_browser->getRealm()) == strtolower($realm), + $message); + } + + /** + * Checks each header line for the required value. If no + * value is given then only an existence check is made. + * @param string $header Case insensitive header name. + * @param string $value Case sensitive trimmed string to + * match against. + * @return boolean True if pass. + * @access public + */ + function assertHeader($header, $value = false, $message = '%s') { + return $this->assertExpectation( + new HttpHeaderExpectation($header, $value), + $this->_browser->getHeaders(), + $message); + } + + /** + * Checks each header line for the required pattern. + * @param string $header Case insensitive header name. + * @param string $pattern Pattern to match value against. + * @return boolean True if pass. + * @access public + */ + function assertHeaderPattern($header, $pattern, $message = '%s') { + return $this->assertExpectation( + new HttpHeaderPatternExpectation($header, $pattern), + $this->_browser->getHeaders(), + $message); + } + + /** + * Confirms that the header type has not been received. + * Only the landing page is checked. If you want to check + * redirect pages, then you should limit redirects so + * as to capture the page you want. + * @param string $header Case insensitive header name. + * @return boolean True if pass. + * @access public + */ + function assertNoUnwantedHeader($header, $message = '%s') { + return $this->assertExpectation( + new HttpUnwantedHeaderExpectation($header), + $this->_browser->getHeaders(), + $message); + } + + /** + * Tests the text between the title tags. + * @param string $title Expected title or empty + * if expecting no title. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertTitle($title = false, $message = '%s') { + return $this->assertTrue( + $title === $this->_browser->getTitle(), + sprintf( + $message, + "Expecting title [$title] got [" . $this->_browser->getTitle() . "]")); + } + + /** + * Will trigger a pass if the Perl regex pattern + * is found in the raw content. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertWantedPattern($pattern, $message = '%s') { + return $this->assertExpectation( + new WantedPatternExpectation($pattern), + $this->_browser->getContent(), + $message); + } + + /** + * Will trigger a pass if the perl regex pattern + * is not present in raw content. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoUnwantedPattern($pattern, $message = "%s") { + return $this->assertExpectation( + new UnwantedPatternExpectation($pattern), + $this->_browser->getContent(), + $message); + } + + /** + * Checks that a cookie is set for the current page + * and optionally checks the value. + * @param string $name Name of cookie to test. + * @param string $expected Expected value as a string or + * false if any value will do. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertCookie($name, $expected = false, $message = "%s") { + $value = $this->_browser->getCurrentCookieValue($name); + if ($expected) { + return $this->assertTrue($value === $expected, sprintf( + $message, + "Expecting cookie [$name] value [$expected], got [$value]")); + } else { + return $this->assertTrue( + $value, + sprintf($message, "Expecting cookie [$name]")); + } + } + + /** + * Checks that no cookie is present or that it has + * been successfully cleared. + * @param string $name Name of cookie to test. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoCookie($name, $message = "%s") { + return $this->assertTrue( + $this->_browser->getCurrentCookieValue($name) === false, + sprintf($message, "Not expecting cookie [$name]")); + } + } +?> \ No newline at end of file diff --git a/htdocs/TESTS/simpletest/xml.php b/htdocs/TESTS/simpletest/xml.php new file mode 100644 index 0000000..bcd74db --- /dev/null +++ b/htdocs/TESTS/simpletest/xml.php @@ -0,0 +1,615 @@ +SimpleReporter(); + $this->_namespace = ($namespace ? $namespace . ':' : ''); + $this->_indent = $indent; + } + + /** + * Calculates the pretty printing indent level + * from the current level of nesting. + * @param integer $offset Extra indenting level. + * @return string Leading space. + * @access protected + */ + function _getIndent($offset = 0) { + return str_repeat( + $this->_indent, + count($this->getTestList()) + $offset); + } + + /** + * Converts character string to parsed XML + * entities string. + * @param string text Unparsed character data. + * @return string Parsed character data. + * @access public + */ + function toParsedXml($text) { + return str_replace( + array('&', '<', '>', '"', '\''), + array('&', '<', '>', '"', '''), + $text); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + parent::paintGroupStart($test_name, $size); + print $this->_getIndent(); + print "<" . $this->_namespace . "group size=\"$size\">\n"; + print $this->_getIndent(1); + print "<" . $this->_namespace . "name>" . + $this->toParsedXml($test_name) . + "_namespace . "name>\n"; + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintGroupEnd($test_name) { + print $this->_getIndent(); + print "_namespace . "group>\n"; + parent::paintGroupEnd($test_name); + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + parent::paintCaseStart($test_name); + print $this->_getIndent(); + print "<" . $this->_namespace . "case>\n"; + print $this->_getIndent(1); + print "<" . $this->_namespace . "name>" . + $this->toParsedXml($test_name) . + "_namespace . "name>\n"; + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintCaseEnd($test_name) { + print $this->_getIndent(); + print "_namespace . "case>\n"; + parent::paintCaseEnd($test_name); + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + parent::paintMethodStart($test_name); + print $this->_getIndent(); + print "<" . $this->_namespace . "test>\n"; + print $this->_getIndent(1); + print "<" . $this->_namespace . "name>" . + $this->toParsedXml($test_name) . + "_namespace . "name>\n"; + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test that is ending. + * @param integer $progress Number of test cases ending. + * @access public + */ + function paintMethodEnd($test_name) { + print $this->_getIndent(); + print "_namespace . "test>\n"; + parent::paintMethodEnd($test_name); + } + + /** + * Increments the pass count. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + parent::paintPass($message); + print $this->_getIndent(1); + print "<" . $this->_namespace . "pass>"; + print $this->toParsedXml($message); + print "_namespace . "pass>\n"; + } + + /** + * Increments the fail count. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print $this->_getIndent(1); + print "<" . $this->_namespace . "fail>"; + print $this->toParsedXml($message); + print "_namespace . "fail>\n"; + } + + /** + * Paints a PHP error or exception. + * @param string $message Message is ignored. + * @access public + * @abstract + */ + function paintException($message) { + parent::paintException($message); + print $this->_getIndent(1); + print "<" . $this->_namespace . "exception>"; + print $this->toParsedXml($message); + print "_namespace . "exception>\n"; + } + + /** + * Paints a simple supplementary message. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + parent::paintMessage($message); + print $this->_getIndent(1); + print "<" . $this->_namespace . "message>"; + print $this->toParsedXml($message); + print "_namespace . "message>\n"; + } + + /** + * Paints a formatted ASCII message such as a + * variable dump. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + parent::paintFormattedMessage($message); + print $this->_getIndent(1); + print "<" . $this->_namespace . "formatted>"; + print ""; + print "_namespace . "formatted>\n"; + } + + /** + * Serialises the event object. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @access public + */ + function paintSignal($type, &$payload) { + parent::paintSignal($type, $payload); + print $this->_getIndent(1); + print "<" . $this->_namespace . "signal type=\"$type\">"; + print ""; + print "_namespace . "signal>\n"; + } + + /** + * Paints the test document header. + * @param string $test_name First test top level + * to start. + * @access public + * @abstract + */ + function paintHeader($test_name) { + if (! SimpleReporter::inCli()) { + header('Content-type: text/xml'); + } + print "_namespace) { + print " xmlns:" . $this->_namespace . + "=\"www.lastcraft.com/SimpleTest/Beta3/Report\""; + } + print "?>\n"; + print "<" . $this->_namespace . "run>\n"; + } + + /** + * Paints the test document footer. + * @param string $test_name The top level test. + * @access public + * @abstract + */ + function paintFooter($test_name) { + print "_namespace . "run>\n"; + } + } + + /** + * Accumulator for incoming tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ + class NestingXmlTag { + var $_name; + var $_attributes; + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingXmlTag($attributes) { + $this->_name = false; + $this->_attributes = $attributes; + } + + /** + * Sets the test case/method name. + * @param string $name Name of test. + * @access public + */ + function setName($name) { + $this->_name = $name; + } + + /** + * Accessor for name. + * @return string Name of test. + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Accessor for attributes. + * @return hash All attributes. + * @access protected + */ + function _getAttributes() { + return $this->_attributes; + } + } + + /** + * Accumulator for incoming method tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ + class NestingMethodTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingMethodTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintMethodStart($this->getName()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintMethodEnd($this->getName()); + } + } + + /** + * Accumulator for incoming case tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ + class NestingCaseTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingCaseTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintCaseStart($this->getName()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintCaseEnd($this->getName()); + } + } + + /** + * Accumulator for incoming group tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ + class NestingGroupTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingGroupTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintGroupStart($this->getName(), $this->getSize()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintGroupEnd($this->getName()); + } + + /** + * The size in the attributes. + * @return integer Value of size attribute or zero. + * @access public + */ + function getSize() { + $attributes = $this->_getAttributes(); + if (isset($attributes['SIZE'])) { + return (integer)$attributes['SIZE']; + } + return 0; + } + } + + /** + * Parser for importing the output of the XmlReporter. + * Dispatches that output to another reporter. + * @package SimpleTest + * @subpackage UnitTester + */ + class SimpleTestXmlParser { + var $_listener; + var $_expat; + var $_tag_stack; + var $_in_content_tag; + var $_content; + var $_attributes; + + /** + * Loads a listener with the SimpleReporter + * interface. + * @param SimpleReporter $listener Listener of tag events. + * @access public + */ + function SimpleTestXmlParser(&$listener) { + $this->_listener = &$listener; + $this->_expat = &$this->_createParser(); + $this->_tag_stack = array(); + $this->_in_content_tag = false; + $this->_content = ''; + $this->_attributes = array(); + } + + /** + * Parses a block of XML sending the results to + * the listener. + * @param string $chunk Block of text to read. + * @return boolean True if valid XML. + * @access public + */ + function parse($chunk) { + if (! xml_parse($this->_expat, $chunk)) { + trigger_error('XML parse error with ' . + xml_error_string(xml_get_error_code($this->_expat))); + return false; + } + return true; + } + + /** + * Sets up expat as the XML parser. + * @return resource Expat handle. + * @access protected + */ + function &_createParser() { + $expat = xml_parser_create(); + xml_set_object($expat, $this); + xml_set_element_handler($expat, '_startElement', '_endElement'); + xml_set_character_data_handler($expat, '_addContent'); + xml_set_default_handler($expat, '_default'); + return $expat; + } + + /** + * Opens a new test nesting level. + * @return NestedXmlTag The group, case or method tag + * to start. + * @access private + */ + function _pushNestingTag($nested) { + array_unshift($this->_tag_stack, $nested); + } + + /** + * Accessor for current test structure tag. + * @return NestedXmlTag The group, case or method tag + * being parsed. + * @access private + */ + function &_getCurrentNestingTag() { + return $this->_tag_stack[0]; + } + + /** + * Ends a nesting tag. + * @return NestedXmlTag The group, case or method tag + * just finished. + * @access private + */ + function _popNestingTag() { + return array_shift($this->_tag_stack); + } + + /** + * Test if tag is a leaf node with only text content. + * @param string $tag XML tag name. + * @return @boolean True if leaf, false if nesting. + * @private + */ + function _isLeaf($tag) { + return in_array( + $tag, + array('NAME', 'PASS', 'FAIL', 'EXCEPTION', 'MESSAGE', 'FORMATTED', 'SIGNAL')); + } + + /** + * Handler for start of event element. + * @param resource $expat Parser handle. + * @param string $tag Element name. + * @param hash $attributes Name value pairs. + * Attributes without content + * are marked as true. + * @access protected + */ + function _startElement($expat, $tag, $attributes) { + $this->_attributes = $attributes; + if ($tag == 'GROUP') { + $this->_pushNestingTag(new NestingGroupTag($attributes)); + } elseif ($tag == 'CASE') { + $this->_pushNestingTag(new NestingCaseTag($attributes)); + } elseif ($tag == 'TEST') { + $this->_pushNestingTag(new NestingMethodTag($attributes)); + } elseif ($this->_isLeaf($tag)) { + $this->_in_content_tag = true; + $this->_content = ''; + } + } + + /** + * End of element event. + * @param resource $expat Parser handle. + * @param string $tag Element name. + * @access protected + */ + function _endElement($expat, $tag) { + $this->_in_content_tag = false; + if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) { + $nesting_tag = $this->_popNestingTag(); + $nesting_tag->paintEnd($this->_listener); + } elseif ($tag == 'NAME') { + $nesting_tag = &$this->_getCurrentNestingTag(); + $nesting_tag->setName($this->_content); + $nesting_tag->paintStart($this->_listener); + } elseif ($tag == 'PASS') { + $this->_listener->paintPass($this->_content); + } elseif ($tag == 'FAIL') { + $this->_listener->paintFail($this->_content); + } elseif ($tag == 'EXCEPTION') { + $this->_listener->paintException($this->_content); + } elseif ($tag == 'SIGNAL') { + $this->_listener->paintSignal( + $this->_attributes['TYPE'], + unserialize($this->_content)); + } elseif ($tag == 'MESSAGE') { + $this->_listener->paintMessage($this->_content); + } elseif ($tag == 'FORMATTED') { + $this->_listener->paintFormattedMessage($this->_content); + } + } + + /** + * Content between start and end elements. + * @param resource $expat Parser handle. + * @param string $text Usually output messages. + * @access protected + */ + function _addContent($expat, $text) { + if ($this->_in_content_tag) { + $this->_content .= $text; + } + return true; + } + + /** + * XML and Doctype handler. Discards all such content. + * @param resource $expat Parser handle. + * @param string $default Text of default content. + * @access protected + */ + function _default($expat, $default) { + } + } +?>