[Checkins] SVN: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/ Adding a new branch to work on:

Mathieu Le Marec - Pasquet kiorky at cryptelium.net
Tue Apr 28 16:13:38 EDT 2009


Log message for revision 99565:
  
  Adding a new branch to work on:
  
      * proxy mode backport from collective.anonymousbrowser
      * integration with mozrunner
      * put in class the screenshot feature
      * make some speed improvments on the firefox based class
      
  

Changed:
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/
  U   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/README.txt
  U   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/buildout.cfg
  U   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/setup.py
  U   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/README.txt
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/authors.txt
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/firefox/
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/firefox/__init__.py
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/firefox/mozrepl.xpi
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.py
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.txt
  U   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/real.py
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/real.txt
  U   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/screen-shots.txt
  U   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/tests.py
  A   zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/utils.py

-=-
Modified: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/README.txt
===================================================================
--- zc.testbrowser/trunk/README.txt	2009-04-23 04:59:07 UTC (rev 99416)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/README.txt	2009-04-28 20:13:37 UTC (rev 99565)
@@ -1,5 +1,5 @@
 Overview
-========
+==========
 
 The zc.testbrowser package provides web user agents (browsers) with
 programmatic interfaces designed to be used for testing web applications,

Modified: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/buildout.cfg
===================================================================
--- zc.testbrowser/trunk/buildout.cfg	2009-04-23 04:59:07 UTC (rev 99416)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/buildout.cfg	2009-04-28 20:13:37 UTC (rev 99565)
@@ -1,29 +1,32 @@
 [buildout]
+extensions=buildout.minitagificator
 develop = .
-parts = test interpreter
+parts = 
+    test 
+    interpreter
 versions = versions
-index = http://download.zope.org/ppix/
 
 [test]
 recipe = zc.recipe.testrunner
 defaults = ['-1', '--auto-color']
-eggs = zc.testbrowser
+eggs = zc.testbrowser [test]
 
 [interpreter]
 recipe = zc.recipe.egg
-eggs = zc.testbrowser
+eggs = zc.testbrowser [test]
 interpreter = py
+scripts=ipython
 
 [versions]
 ClientForm = 0.2.7
 mechanize = 0.1.7b
 setuptools = 0.6c9
 simplejson = 1.7.1
-zc.buildout = 1.1.1
 zc.recipe.egg = 1.1.0
 zc.recipe.testrunner = 1.1.0
 zope.event = 3.4.0
 zope.i18nmessageid = 3.4.0
 zope.interface = 3.4.0
+zc.buildout=1.1.1
 zope.schema = 3.3.0
 zope.testing = 3.7.0

Modified: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/setup.py
===================================================================
--- zc.testbrowser/trunk/setup.py	2009-04-23 04:59:07 UTC (rev 99416)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/setup.py	2009-04-28 20:13:37 UTC (rev 99565)
@@ -16,11 +16,20 @@
 
 long_description = (
     open('README.txt').read()
-    + open('CHANGES.txt').read()
-    + '.. contents::\n\n\n'
-    + open(os.path.join('src', 'zc', 'testbrowser', 'README.txt')).read()
+    + "\n\n%s" % open('CHANGES.txt').read()
+    + "\n\n%s" % '.. contents::\n'
+    + "\n\n%s" % open(os.path.join('src', 'zc', 'testbrowser', 'README.txt')).read()
+    + "\n\n%s" % open(os.path.join('src', 'zc', 'testbrowser', 'real.txt')).read()
+    + "\n\n%s" % open(os.path.join('src', 'zc', 'testbrowser', 'proxy.txt')).read()
+    + "\n\n%s" % open(os.path.join('src', 'zc', 'testbrowser', 'authors.txt')).read()
     )
 
+if 'RSTTEST' in os.environ:
+    open(os.path.expanduser('~/test'), 'w').write(long_description)
+    import sys
+    sys.exit(0)
+
+
 setup(
     name = 'zc.testbrowser',
     version = '1.0.0a6dev',
@@ -43,7 +52,7 @@
     packages = find_packages('src'),
     package_dir = {'': 'src'},
     namespace_packages = ['zc',],
-    tests_require = ['zope.testing'],
+    tests_require = ['zope.testing',],
     install_requires = [
         'ClientForm',
         'mechanize',
@@ -51,8 +60,9 @@
         'simplejson',
         'zope.interface',
         'zope.schema',
+        'mozrunner',
         ],
-
+    extras_require={'test': ['IPython', 'mocker']},
     include_package_data = True,
     zip_safe = False,
     )

Modified: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/README.txt
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/README.txt	2009-04-23 04:59:07 UTC (rev 99416)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/README.txt	2009-04-28 20:13:37 UTC (rev 99565)
@@ -1,5 +1,5 @@
 Detailed Documentation
-======================
+=======================
 
 Before being of much interest, we need to open a web page.  ``Browser``
 instances have a ``base`` attribute that sets the URL from which ``open``-ed
@@ -1277,17 +1277,3 @@
     >>> len(browser.contents) == actual_length
     True
 
-
-Authors
--------
-
-Benji York created testbrowser (originally zope.testbrowser) in 2005 with Gary
-Poster and Stephan Richter making large contributions.
-
-The zc.testbrowser.real version was conceptualized by Benji York in 2007 and
-after an initial implementation sketch, brought to fruition by Stephan
-Richter, Rocky Burt, Justas Sadzevicius, and others at the Foliage Zope 3
-sprint in Boston, MA during the week of September 24, 2007.
-
-There have been many other contributions from users of testbrowser that are
-greatly appreciated.

Added: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/authors.txt
===================================================================
--- zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/authors.txt	                        (rev 0)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/authors.txt	2009-04-28 20:13:37 UTC (rev 99565)
@@ -0,0 +1,16 @@
+Authors
+========
+
+Benji York created testbrowser (originally zope.testbrowser) in 2005 with Gary
+Poster and Stephan Richter making large contributions.
+
+The zc.testbrowser.real version was conceptualized by Benji York in 2007 and
+after an initial implementation sketch, brought to fruition by Stephan
+Richter, Rocky Burt, Justas Sadzevicius, and others at the Foliage Zope 3
+sprint in Boston, MA during the week of September 24, 2007.
+
+Mathieu Pasquet added recently proxy support and improved the real browser class.
+
+There have been many other contributions from users of testbrowser that are
+greatly appreciated.
+

Added: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/firefox/mozrepl.xpi
===================================================================
(Binary files differ)


Property changes on: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/firefox/mozrepl.xpi
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.py
===================================================================
--- zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.py	                        (rev 0)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.py	2009-04-28 20:13:37 UTC (rev 99565)
@@ -0,0 +1,299 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+__docformat__ = "reStructuredText"
+
+import ConfigParser
+import logging
+import mechanize
+import os
+import random
+import sys
+import time
+
+from zc.testbrowser import browser, real, utils
+
+FF2_USERAGENT = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; '\
+        'fr; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14'
+
+class BaseAnonymousBrowser:
+    """A base class to implement powerful and anonymous browser from.
+    This abstract classr may be used to write browsers that support proxies
+    balancing
+
+    """
+    def __init__(self,
+                 url = None,
+                 config = None,
+                 proxies = None,
+                 user_agent = None,
+                 proxy_max_use = None,
+                 mech_browser = None,
+                 *args, **kwargs):
+        self._enable_setattr_errors = False
+        if not proxies:
+            proxies = []
+        if not getattr(self, 'log', None):
+            self.log = logging.getLogger(__name__)
+        self.load_config(config)
+        proxieslist = self.config.get('proxies', '').strip()
+        self.proxies = [proxy.strip()
+                        for proxy in proxieslist.split('\n')
+                        if proxy.strip()]
+        self.proxies.extend([p
+                           for p in proxies
+                           if not p in self.proxies])
+        if user_agent:
+            self.user_agent = user_agent
+        else:
+            self.user_agent = self.config.get('user-agent',
+                                               FF2_USERAGENT)
+        # only enter proxified mode when we got a list of valid proxies
+        self.proxified = False
+        if self.proxies:
+            self.proxified = True
+
+        # intialize proxy balancing
+        self._lastproxy = {'proxy':-1,
+                           'count':0}
+        # initiate proxy max use
+        self.proxy_max_use = proxy_max_use
+
+    def load_config(self, config):
+        if not getattr(self, 'config', None):
+            self.config = utils.get_ztb_config(config)
+
+    def chooseProxy(self):
+        choice = 0
+        if len(self.proxies) < 2:
+            # for 0 or 1 proxy, just get it
+            choice = random.randint(0, len(self.proxies)-1)
+        else:
+            # for 2+ proxies in the list, we iterate to get a different proxy
+            # for the last one used, if this one was too many used.
+            # We also put a coin of the reuse of the proxy, we just dont go too
+            # random
+            proxy_not_chosen, maxloop = True, 200
+            while proxy_not_chosen:
+                # pile or face ! We reuse the proxy or not!
+                if not self.proxy_max_use \
+                   or (self._lastproxy['count'] >= 1) \
+                   and (self._lastproxy['count'] < self.proxy_max_use):
+                    # we do not always change proxy
+                    if (self._lastproxy['proxy'] != -1) and random.randint(0, 1):
+                        choice = self._lastproxy['proxy']
+                    else:
+                        choice = random.randint(0, len(self.proxies)-1)
+                if self._lastproxy['proxy'] == choice:
+                    if not self.proxy_max_use or (self._lastproxy['count'] <= self.proxy_max_use):
+                        self._lastproxy['proxy'] = choice
+                        proxy_not_chosen = False
+                else:
+                    self._lastproxy['proxy'] = choice
+                    # reinitialize the proxy count
+                    self._lastproxy['count'] = 0
+                    proxy_not_chosen = False
+                maxloop -= 1
+                if not maxloop:
+                    self.log.debug("Ho, seems we got the max wills "
+                                   "to choose, something has gone wrong")
+                    proxy_not_chosen = False
+        self._lastproxy['count'] += 1
+        return self.proxies[choice]
+
+    def reset(self, url, data):
+        """Reset the underlying browser to its initial state.
+        (eg, just after init)
+        Implementation example::
+
+            try:
+                self.browser_open('about:blank')
+            except Exception, e:
+                if not ('unknown url type:' in '%s' % e):
+                    raise e
+        """
+        raise Exception('not implemented')
+
+    def open(self, url, data=None, retrys=4, *args, **kwargs):
+        """Wrapper to the underlyng browser class.
+        It will do the neccessary things to proxisy the
+        browser before hitting any url.
+        @param url the url to hit
+        @param retrys number of retrys allowed before crashing.
+        """
+        try:
+            if self.proxified:
+                self.proxify()
+            if self.user_agent:
+                self.fake_user_agent()
+            return self.browser_open(url, data)
+        except Exception, e:
+            if retrys:
+                # go to open blank to reset entirely all post and other stuff
+                self.reset(url, data)
+
+                # removing dead proxies
+                if self.proxified:
+                    if len(self.proxies) >= 1:
+                        del self.proxies[self._lastproxy['proxy']]
+                        self._lastproxy['proxy'] = -1
+                        self._lastproxy['count'] = 0
+                    else:
+                        raise Exception("There are no valid proxies left")
+
+                self.log.error('Retrying "%s", (left: %s)' % (url, retrys))
+                retrys -= 1
+                self.open(url, data, retrys)
+            else:
+                raise e
+
+    def proxify(self, force=False):
+        """Method to choose and set a proxy."""
+        raise Exception('not implemented')
+
+    def fake_user_agent(self, force=False):
+        """Method to set the user agent."""
+        raise Exception('not implemented')
+
+    def browser_open(self, url, data=None):
+        """Real method to hit the browser.
+        Implementation example ::
+
+            browser.Browser.open(self, url, data)
+
+        """
+        raise Exception('not implemented')
+
+class AnonymousBrowser(BaseAnonymousBrowser, browser.Browser):
+    """A proxified mechanize based browser."""
+
+    def __init__(self,
+                 url=None,
+                 config=None,
+                 mech_browser=None,
+                 proxies = [],
+                 user_agent=None,
+                 proxy_max_use=None,
+                 *args, **kwargs):
+        BaseAnonymousBrowser.__init__(self,
+                                      url = url,
+                                      config = config,
+                                      proxies = proxies,
+                                      user_agent=user_agent,
+                                      proxy_max_use=proxy_max_use,
+                                      *args, **kwargs)
+        self._enable_setattr_errors = False
+        self.test=kwargs.get('test',False)
+        if mech_browser is None:
+            mech_browser = mechanize.Browser()
+        self.mech_browser = mech_browser
+        if url is not None:
+            BaseAnonymousBrowser.open(self, url)
+
+    def reset(self, url, data):
+        pass
+
+    def fake_user_agent(self, force=False):
+        self.mech_browser.set_handle_robots(False)
+        self.mech_browser.addheaders = [('User-agent' , self.user_agent)]
+
+    def browser_open(self, url, data=None):
+        self.timer = browser.PystoneTimer()
+        self.raiseHttpErrors = True
+        return browser.Browser.open(self, url, data)
+
+    def proxify(self, force=False):
+        """"""
+        if (self.proxified or force) and self.proxies:
+            proxy = self.chooseProxy()
+            self.mech_browser.set_proxies(
+                {'http': proxy,
+                 'https': proxy}
+            )
+
+class FirefoxBrowser(BaseAnonymousBrowser, real.Browser):
+    """A proxified mechanize based browser."""
+
+    def __init__(self,
+                 url=None,
+                 host = None,
+                 port=None,
+                 firefox_binary = None,
+                 profile_klass = None,
+                 config=None,
+                 proxies = [],
+                 user_agent=None,
+                 proxy_max_use=None,
+                 *args, **kwargs):
+        BaseAnonymousBrowser.__init__(self,
+                                      url = url,
+                                      config = config,
+                                      proxies = proxies,
+                                      user_agent=user_agent,
+                                      proxy_max_use=proxy_max_use,
+                                      *args, **kwargs)
+        preferences= {}
+        if self.user_agent:
+            preferences["general.useragent.override"] =  self.user_agent
+        real.Browser. __init__(self,
+                               url=None,
+                               host = host,
+                               port=port,
+                               firefox_binary = firefox_binary,
+                               profile_klass = profile_klass,
+                               config=config,
+                               preferences = preferences,
+                               *args, **kwargs)
+
+        if url is not None:
+            BaseAnonymousBrowser.open(self, url)
+
+    def browser_open(self, url, data=None):
+        return real.Browser.open(self, url, data)
+
+    def fake_user_agent(self, force=False):
+        """Done in __init__, you cannont change it without restarting firefox"""
+        if force:
+            user_agent_prefs = {"general.useragent.override": self.user_agent}
+            self.update_profile(user_agent_prefs)
+            self.restart_ff()
+
+    def reset(self, url, data):
+        self.restart_ff()
+        self.open(url, data)
+
+    def browser_open(self, url, data=None):
+        return real.Browser.open(self, url, data)
+
+    def proxify(self, force=False):
+        """"""
+        if (self.proxified or force) and self.proxies:
+            host, port = self.chooseProxy().split(':')
+            proxy_prefs = {
+                'network.proxy.http':   host,
+                'network.proxy.ftp':    host,
+                'network.proxy.gopher': host,
+                'network.proxy.socks':  host,
+                'network.proxy.ssl':    host,
+                'network.proxy.http_port':   port,
+                'network.proxy.ftp_port':    port,
+                'network.proxy.gopher_port': port,
+                'network.proxy.socks_port':  port,
+                'network.proxy.ssl_port':    port,
+                'network.proxy.type': 1,
+                'network.proxy.no_proxy_on': '',
+            }
+            self.update_profile(proxy_prefs)
+
+
+

Added: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.txt
===================================================================
--- zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.txt	                        (rev 0)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/proxy.txt	2009-04-28 20:13:37 UTC (rev 99565)
@@ -0,0 +1,290 @@
+The proxified browsers
+=======================
+
+
+A proxified abstract class
+---------------------------
+
+We provide an abstract class to enable you to easily develop your own specialized proxies.
+All that you need to do is to inherit this class, and your browser one, and then play with the ``browser_open`` method.
+
+
+We need to monkey patch expand user not to alterate our configuration files in ~/
+
+    >>> config = tempfile.mkstemp()[1]
+    >>> remove(config)
+    >>> zc.testbrowser.utils.__CONFIGFILE__  = config
+
+Install some magic to get the logs
+
+    >>> from zope.testing.loggingsupport import InstalledHandler
+    >>> log_handler = InstalledHandler('zc.testbrowser.proxy')
+
+Here is how you can implement a proxified class
+
+    >>> class MyBrowser:
+    ...     """My original browser"""
+    ...     def __init__(self):
+    ...         """."""
+    ...     def open(self, *args, **kwargs):
+    ...         print 'But I ll do the same proxified or not!'
+    >>>
+
+Heritage order is important !!!
+
+    >>> class ProxifiedBrowser(BaseAnonymousBrowser, MyBrowser):
+    ...    """."""
+    ...    def __init__(self, proxies=None, *args, **kwargs):
+    ...
+    ...        # in the real life, specify real constructor arguments
+    ...        BaseAnonymousBrowser.__init__(self, proxies=proxies)
+    ...        MyBrowser.__init__(self)
+    ...
+    ...    def reset(self):
+    ...         pass
+    ...    def fake_user_agent(self, force=False):
+    ...         print "ua:%s" % self.user_agent
+    ...    def proxify(self, force=False):
+    ...         print "proxies:%s" % self.proxies
+    ...    def browser_open(self, *args, **kwargs):
+    ...        print 'I am wrapped'
+    ...        return MyBrowser.open(self, *args, **kwargs)
+    >>>
+
+And when you instantiate it, here is the magic
+
+   >>> mybrowser = ProxifiedBrowser(proxies=['a'])
+   >>> mybrowser.open('')
+   proxies:['a']
+   ua:Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14
+   I am wrapped
+   But I ll do the same proxified or not!
+
+
+But, we want to be anonymous, and we ll set a proxy
+To define those proxies, Either:
+
+    * Set  a config.ini file like::
+
+        [zc.testbrowser]
+        proxies =
+            host1:port
+            host2:port
+
+    * give a ``proxies`` list to the browser constructor.
+
+        >>> b = BaseAnonymousBrowser(proxies=['http://foo.net:3128'])
+        >>> b.proxies
+        ['http://foo.net:3128']
+
+The first time you launch the browser, if no config is present, it will be created for you in ~/.zc.testbrowser.cfg:
+
+    >>> cat(config)
+    [zc.testbrowser]
+    proxies =
+    <BLANKLINE>
+
+The implementation of the abstract class is somehow, ..., limited
+
+    >>> b.browser_open('')
+    Traceback (most recent call last):
+    Exception: not implemented
+    >>> b.proxify()
+    Traceback (most recent call last):
+    Exception: not implemented
+    >>> b.fake_user_agent()
+    Traceback (most recent call last):
+    Exception: not implemented
+    >>> b.reset(None, None)
+    Traceback (most recent call last):
+    Exception: not implemented
+
+When the browser has many proxies defined, it will circly through those ones.
+But, it will not use the same host indefinitivly, just set the ``proxy_max_use`` argument::
+
+    >>> sproxies = [proxy.replace('http:', '').replace('/', '') for proxy in proxies]
+    >>> proxy = sproxies[0]
+    >>> url = '%s/print_request' % proxies[0]
+    >>> b = AnonymousBrowser(proxies=sproxies, proxy_max_use=3)
+    >>> b.config
+    {'__name__': 'zc.testbrowser', 'proxies': ''}
+    >>> b.proxies == sproxies
+    True
+    >>> b.proxified
+    True
+
+
+The most important of the class is the proxy balancing mode.
+Naturally, a thing to verify is that we have our pseudo-random loop running.
+First thing is we will choose 2 times the 2nd proxy, then the third
+And of course, we will set the mocker to change the proxy at each row.
+
+    >>> import mocker
+    >>> import random
+    >>> mocked = mocker.Mocker()
+    >>> custom_random_int = mocked.replace('random.randint')
+    >>> custom_random_int(0, 3)
+    <mocker.Mock object at ...>
+    >>> mocked.result(2)
+    >>> custom_random_int(0,1)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> custom_random_int(0, 3)
+    <mocker.Mock object at ...>
+    >>> mocked.result(2)
+    >>> custom_random_int(0,1)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> custom_random_int(0, 3)
+    <mocker.Mock object at ...>
+    >>> mocked.result(2)
+    >>> custom_random_int(0,1)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> custom_random_int(0, 3)
+    <mocker.Mock object at ...>
+    >>> mocked.result(3)
+    >>> custom_random_int(0,1)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> custom_random_int(0, 3)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> custom_random_int(0,1)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> custom_random_int(0, 3)
+    <mocker.Mock object at ...>
+    >>> mocked.result(2)
+    >>> custom_random_int(0,1)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> custom_random_int(0, 3)
+    <mocker.Mock object at ...>
+    >>> mocked.result(1)
+    >>> custom_random_int(0,1)
+    <mocker.Mock object at ...>
+    >>> mocked.result(0)
+    >>> mocked.replay()
+
+Live !
+
+    >>> b = AnonymousBrowser(url=url, proxies=sproxies, proxy_max_use=3)
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 1, 'proxy': 2}
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 2, 'proxy': 2}
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 3, 'proxy': 2}
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 1, 'proxy': 0}
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 1, 'proxy': 3}
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 1, 'proxy': 0}
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 1, 'proxy': 2}
+    >>> b.open(url)
+    >>> b._lastproxy
+    {'count': 1, 'proxy': 1}
+    >>> mocked.restore()
+
+If the proxies are dead, we just remove them from the list.
+
+    >>> wproxies = [ 'localhost:35675', 'localhost:35676', 'localhost:35677', proxy,]
+    >>> mybrowser = AnonymousBrowser(proxies=wproxies, proxy_max_use=3)
+    >>> mybrowser.proxies
+    ['localhost:35675', 'localhost:35676', 'localhost:35677', 'localhost:...']
+    >>> mybrowser.open(url)
+    >>> mybrowser.proxies
+    ['localhost:...']
+
+This may arrive, your proxies list is totally invalid, no proxy can be reached
+
+    >>> mybrowser.proxies = ['localhost:invalid']
+    >>> mybrowser.open(url)
+    Traceback (most recent call last):
+    ...
+    Exception: There are no valid proxies left
+
+
+The loop is recursion protected. If we return always the same host, so the chooser cannot choose anything else.
+It will loop until it crashes or it handle the recursion::
+
+    >>> def randomint(a,b):
+    ...     return 2
+    >>> import random; random.randint = randomint
+    >>> b2 = AnonymousBrowser(proxies=sproxies, proxy_max_use=3)
+    >>> b2.proxy_max_use
+    3
+    >>> b2._lastproxy['count']
+    0
+    >>> b2.chooseProxy()
+    '...
+    >>> b2._lastproxy['count']
+    1
+    >>> b2.chooseProxy()
+    '...
+    >>> b2._lastproxy['count']
+    2
+    >>> b2.chooseProxy()
+    '...
+    >>> b2._lastproxy['count']
+    3
+    >>> b2.chooseProxy()
+    '...
+    >>> b2.chooseProxy()
+    'localhost:...'
+    >>> log_handler.records[-1].msg
+    'Ho, seems we got the max wills to choose, something has gone wrong'
+
+Controlling automaticly the User Agent
+------------------------------------------
+Hay, we have a brand new default user agent::
+
+    >>> br = AnonymousBrowser()
+    >>> br.open(url)
+    >>> FF2_USERAGENT in br.contents
+    True
+    >>> br2 = AnonymousBrowser(url)
+    >>> FF2_USERAGENT in br2.contents
+    True
+
+The mechanize proxified browser
+----------------------------------
+Use this browser to have a proxified version of the
+basic mechanize browser (zc.testbrowser.browser.Browser)
+
+    >>> b = AnonymousBrowser(proxies=sproxies,**{"test":True})
+    >>> b.open('%s/print_request' % url)
+    >>> 'Host: localhost:' in b.contents
+    True
+    >>> b._lastproxy['count'] == 1
+    True
+    >>> b._lastproxy['proxy'] in [0,1,2,3,4]
+    True
+
+The firefox based anonymous browser
+---------------------------------------
+
+    >>> noecho = os.system("ps aux|grep firefox|awk '{print $2}'|xargs kill -9")
+    >>> ff = FirefoxBrowser(url=url, proxies = sproxies)
+    >>> 'Host: localhost:' in ff.contents
+    True
+    >>> FF2_USERAGENT in ff.contents
+    True
+    >>> ff.stop_ff()
+
+Cleanup
+-----------
+
+    >>> remove(config)
+    >>> remove(ff.firefox_profile.profile)
+

Modified: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/real.py
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/real.py	2009-04-23 04:59:07 UTC (rev 99416)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/real.py	2009-04-28 20:13:37 UTC (rev 99565)
@@ -1,3 +1,18 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+import logging
+import os
 import os.path
 import re
 import simplejson
@@ -4,15 +19,75 @@
 import socket
 import telnetlib
 import tempfile
+import signal
 import time
 import urlparse
+
 import zc.testbrowser.browser
 import zc.testbrowser.interfaces
+from zc.testbrowser import utils
+
 import zope.interface
 
+import mozrunner
+
 PROMPT = re.compile('repl\d?> ')
 CONTINUATION_PROMPT = re.compile('\\.\\.\\.\\.\d?> ')
 
+class MozReplProfile(mozrunner.FirefoxProfile):
+    """A mozrepl enabled firefox profile"""
+
+    def set_preferences(self, preferences):
+        """Adds preferences dict to profile preferences"""
+        pref_lines = ['user_pref(%s, %s);' %
+                      (simplejson.dumps(k),
+                       simplejson.dumps(v).replace('\/', '/' )) for k, v in
+                      preferences.items()]
+        prefs_file = os.path.join(self.profile, 'user.js')
+        gen_prefs_file = os.path.join(self.profile, 'prefs.js')
+        for fp in prefs_file, gen_prefs_file:
+            f = open(fp, 'a+')
+            f.write('\n#MozRunner Prefs Start\n')
+            for line in pref_lines:
+                f.write(line+'\n')
+            f.write('#MozRunner Prefs End\n')
+            f.flush()
+            f.close()
+
+    def __init__(self,
+                 default_profile=None,
+                 profile=None,
+                 create_new=True,
+                 plugins=[],
+                 preferences={},
+                 port=4242,
+                 *args, **kwargs):
+        """See mozrunner code for documentation.
+        port: port where mozrepl should listen on"""
+        plugins.append(
+            os.path.join(os.path.dirname(__file__), 'firefox', 'mozrepl.xpi')
+        )
+        # update preferences to activate mozrepl on firefox startup
+        # depending the mozrepl version, keys can changed!
+        self.preferences.update(
+            {'extensions.mozlab.mozrepl.autoStart': True,
+             'extensions.mozlab.mozrepl.loopbackOnly': False,
+             'extensions.mozlab.mozrepl.port': port,
+             'extensions.mozlab.mozrepl.autoStart': True,
+             'extensions.mozrepl.autoStart': True,
+             'extensions.mozrepl.loopbackOnly': False,
+             'extensions.mozrepl.port': port,
+             'extensions.mozrepl.autoStart': True,
+            }
+        )
+        mozrunner.FirefoxProfile.__init__(self,
+                                          default_profile=default_profile,
+                                          profile=profile,
+                                          create_new=create_new,
+                                          plugins=plugins,
+                                          preferences=preferences,
+                                          *args, **kwargs)
+
 class BrowserStateError(RuntimeError):
     pass
 
@@ -20,7 +95,6 @@
 class AmbiguityError(ValueError):
     pass
 
-
 def disambiguate(intermediate, msg, index):
     if intermediate:
         if index is None:
@@ -94,6 +168,9 @@
 
 
 class Browser(zc.testbrowser.browser.SetattrErrorsMixin):
+    """A firefox based browser controller.
+    It supports firefox launching through mozrunner if the host is localhost
+    """
 
     zope.interface.implements(zc.testbrowser.interfaces.IBrowser,
         zc.testbrowser.interfaces.IWait)
@@ -103,17 +180,58 @@
     _counter = 0
     timeout = 60
 
-    def __init__(self, url=None, host='localhost', port=4242):
+    def __init__(self,
+                 url=None,
+                 host = None,
+                 port=None,
+                 firefox_binary = None,
+                 profile_klass = None,
+                 config=None,
+                 preferences = None,
+                 *args, **kwargs):
+
+        self._enable_setattr_errors = False
+        self.log = logging.getLogger(__name__)
         self.js = JSProxy(self.execute)
-        self.init_repl(host, port)
-        self._enable_setattr_errors = True
+        self.config = utils.get_ztb_config(config)
+        self.host = host
+        self.port = port
+        if not profile_klass:
+            profile_klass = MozReplProfile
+        if not preferences:
+            preferences = {}
+        # those are path location only !
+        self.MOZILLA_BINARY = firefox_binary
+        if not self.host:
+            self.host = self.config.get('firefox-host', 'localhost')
+        if not port:
+            self.port = self.config.get('firefox-port',  4242)
+        if not self.MOZILLA_BINARY:
+            self.MOZILLA_BINARY = self.config.get('firefox',
+                os.environ.get('FIREFOX', 'firefox')
+            ).strip()
+        # for local addresses, just load directly the file, its really faster
+        hosts = socket.gethostbyaddr(socket.getaddrinfo(self.host, 0)[0][4][0])[1]
 
+        self.is_local = 'localhost' in hosts
+        # if we are local, we are just enought smarts to handle firefox
+        # starts/stops
+        self.firefox_profile, self.firefox = None, None
+        if self.is_local:
+            self.firefox_profile = profile_klass(port=self.port, preferences=preferences)
+            self.firefox = mozrunner.FirefoxRunner(binary=utils.which(self.MOZILLA_BINARY),
+                                                   profile=self.firefox_profile)
+        self.firefox_running = False
+        # this will be a no-op if we are not attacking a local mozrepl.
+        self.start_ff()
         if url is not None:
             self.open(url)
 
     def init_repl(self, host, port):
         try:
+            self._enable_setattr_errors = False
             self.telnet = telnetlib.Telnet(host, port)
+            self._enable_setattr_errors = True
         except socket.error, e:
             raise RuntimeError('Error connecting to Firefox at %s:%s.'
                 ' Is MozRepl running?' % (host, port))
@@ -125,21 +243,30 @@
         self.load_file(js_path)
 
     def load_file(self, file_path):
-        for line in open(file_path, 'r'):
-            self.telnet.write(line)
-            self.expect([PROMPT, CONTINUATION_PROMPT])
+        # for local addresses, just load directly the file, its really faster
+        self.assert_repl_initialized()
+        hosts = socket.gethostbyaddr(
+            socket.getaddrinfo(self.telnet.host, 0)[0][4][0]
+        )[1]
+        if self.is_local:
+            self.telnet.write('repl.load("file://%s")' % file_path)
+        else:
+            for line in open(file_path, 'r'):
+                self.telnet.write(line)
+        self.expect([PROMPT, CONTINUATION_PROMPT])
 
     def execute(self, js):
+        self.assert_repl_initialized()
         if not js.strip():
             return
         if not js.endswith('\n'):
             js = js + '\n'
-        self.telnet.write("'MARKER'\n")
-        self.telnet.read_until('MARKER', self.timeout)
+        # wipe the line from previous dusts in the channel
+        self.telnet.write("\n;\n")
+        #print self.telnet.read_until('MARKER', self.timeout)
         self.expect()
         self.telnet.write(js)
         i, match, text = self.expect()
-        #if '!!!' in text: import pdb;pdb.set_trace() # XXX debug only, remove
         if '!!!' in text: raise Exception('FAILED: ' + text + ' in ' + js)
         result = text.rsplit('\n', 1)
         if len(result) == 1:
@@ -159,7 +286,11 @@
     def getAttributes(self, tokens, attr_name):
         return self.js.tb_extract_token_attrs(tokens, attr_name)
 
+    def assert_repl_initialized(self):
+        assert getattr(self, 'telnet', None), 'MozRepl is not initialized'
+
     def expect(self, expected=None):
+        self.assert_repl_initialized()
         if expected is None:
             expected = [PROMPT]
         i, match, text = self.telnet.expect(expected, self.timeout)
@@ -185,6 +316,14 @@
             url = url.split(token)[0]
         return url
 
+
+    def update_profile(self, prefs=None):
+        if prefs:
+            self.firefox.profile.preferences.update(prefs)
+        self.firefox_profile.set_preferences(
+            self.firefox_profile.preferences
+        )
+
     def wait(self):
         start = time.time()
         while self.execute('tb_page_loaded') == 'false':
@@ -192,7 +331,14 @@
             if time.time() - start > self.timeout:
                 raise RuntimeError('timed out waiting for page load')
 
-        assert self.execute('tb_page_loaded;') == 'true'
+        #ret = self.execute('tb_page_loaded;')
+        retrys = 100
+        assertion = None
+        while retrys and not assertion:
+            retrys -= 1
+            time.sleep(0.01)
+            assertion = self.execute('tb_page_loaded;')
+        assert assertion == 'true'
 
         while self.execute('tb_page_loaded;') == 'true':
             self.execute('tb_page_loaded = false;')
@@ -210,8 +356,6 @@
         finally:
             self._changed()
 
-        # TODO raise non-200 errors
-
     @property
     def isHtml(self):
         return self.execute('content.document.contentType') == 'text/html'
@@ -229,10 +373,8 @@
     @property
     def contents(self):
         base, sub = self.execute('content.document.contentType').split('/')
-        if base == 'text' and 'html' not in sub:
-            return self.execute(
-                "content.document.getElementsByTagName('pre')[0].innerHTML")
-        return self.execute('tb_get_contents()')
+        command = 'tb_get_contents()'
+        return self.execute(command)
 
     def reload(self):
         self.execute('content.document.location = content.document.location')
@@ -327,7 +469,107 @@
 
         return Form(self, form_token)
 
+    def exec_contentjs(self, cmd):
+        d = {'s': self.getPrompt(), 'cmd': cmd}
+        try:
+            self.home()
+            ret = self.execute("this.content.%(cmd)s; " % d)
+        except Exception, e:
+            raise
+        finally:
+            self.home()
+        return ret
 
+    def quit_mozrepl(self):
+        self._enable_setattr_errors = False
+        self._prompt = None
+        try:
+            self.telnet.close()
+        except Exception, e:
+            pass
+        try:
+            delattr(self, 'telnet')
+        except Exception, e:
+            pass
+        self._enable_setattr_errors = True
+
+    def home(self):
+        self.execute("%s.home()" % self.getPrompt())
+
+    def getPrompt(self, force = True):
+        self.assert_repl_initialized()
+        self.telnet.write("\n;\n")
+        if not getattr(self, '_prompt', None) or force:
+            a, g, c = self.expect()
+            self._enable_setattr_errors = False
+            self._prompt = re.sub('> .*', '', g.group())
+            self._enable_setattr_errors = True
+        return getattr(self, '_prompt', 'repl')
+
+    def exec_in_ff_dir(self, f, *args, **kwargs):
+        """Runs the runner in the firefox directory, if any
+        If firefox is not runned on a standart location,
+        there are chances you ll get dynamic libraries problems.
+        Solution is to be chdir'ed prior to the firefox launch"""
+        ret, cwd = None, os.getcwd()
+        if self.is_local:
+            d = os.path.dirname(self.firefox.binary)
+            os.chdir(d)
+            try:
+                ret = f(*args, **kwargs)
+                os.chdir(cwd)
+            except Exception, e:
+                os.chdir(cwd)
+                raise
+        return ret
+
+    def start_ff(self):
+        """Starts firefox, only if firefox isnt running already."""
+        if not self.firefox_running:
+            self.exec_in_ff_dir(self.firefox.start)
+        time.sleep(1)
+        retry = 60
+        while retry:
+            retry -= 1
+            time.sleep(1)
+            try:
+                self.init_repl(self.host, self.port)
+                retry = False
+            except Exception, e:
+                if not retry:
+                    raise
+        # considere that firefox is running only if both the couple:
+        # firefox / MozRepl connexion is achieved.
+        self.firefox_running = True
+
+    def stop_ff(self):
+        """Stops firefox, only if firefox is running."""
+        if self.firefox_running:
+            self.exec_in_ff_dir(
+                self.firefox.kill,
+                signal.SIGKILL
+            )
+            self.quit_mozrepl()
+            self.firefox_running = False
+
+    def restart_ff(self):
+        self.stop_ff()
+        time.sleep(1)
+        self.start_ff()
+        time.sleep(1)
+
+    def screenshot(self, path=".", name="screenshot"):
+        if not os.path.exists(path):
+            os.makedirs(path)
+        fpath = os.path.join(path, "%s.png" % name)
+        self.execute('tb_take_screen_shot("%s")' % fpath)
+        timeout = 12000
+        while not os.path.isfile(fpath) or not timeout:
+            timeout -= 1
+            time.sleep(0.01)
+        if not timeout:
+            raise 'Timeout while taking shot!'
+
 class Link(zc.testbrowser.browser.SetattrErrorsMixin):
     zope.interface.implements(zc.testbrowser.interfaces.ILink)
 

Added: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/real.txt
===================================================================
--- zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/real.txt	                        (rev 0)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/real.txt	2009-04-28 20:13:37 UTC (rev 99565)
@@ -0,0 +1,91 @@
+
+Controlling rael browsers: the firefox case
+=====================================================
+We will use an extension, **mozrepl**, to control firefox.
+It's a little piece of software that enables you to connect to your firefox via telnet, and controls what's controllable via javascript.
+See http://wiki.github.com/bard/mozrepl for further documentation .
+
+The profile
+-----------------------------
+Configuring firefox can be a heck, we will provide a default profile, whith mozrepl installed a configured to run on localhost/4242.
+We configured also this profile to stop firefox being interactive (eg: the session restore thing)
+
+    >>> profile = MozReplProfile()
+    >>> profile.profile
+    '...mozrunner'
+    >>> profile.preferences
+    {'browser.shell.checkDefaultBrowser': False, 'extensions.mozrepl.loopbackOnly': False, 'browser.sessionstore.resume_from_crash': False, 'browser.warnOnQuit': False, 'extensions.update.enabled': False, 'browser.tabs.warnOnClose': False, 'extensions.update.notifyUser': False, 'extensions.mozlab.mozrepl.port': 4242, 'extensions.mozlab.mozrepl.autoStart': True, 'extensions.mozrepl.autoStart': True, 'extensions.mozlab.mozrepl.loopbackOnly': False, 'extensions.mozrepl.port': 4242}
+
+    >>> profile.plugins
+    ['...zc/testbrowser/firefox/mozrepl.xpi']
+    >>> profile.plugins_installed
+    ['...mozrunner/extensions/mozrepl at hyperstruct.net']
+
+Controlling firefox
+--------------------------------
+
+    >>> noecho = os.system("ps aux|grep firefox|awk '{print $2}'|xargs kill -9")
+    >>> url = '%s/print_request' % proxies[0]
+    >>> b = RealBrowser(url)
+    >>> print b.contents
+    <html>...
+    >>> b.firefox_running
+    True
+
+Stopping it
+--------------------
+
+    >>> b.stop_ff()
+    >>> b.firefox_running
+    False
+    >>> b.contents
+    Traceback (most recent call last):
+    ...
+    AssertionError: MozRepl is not initialized
+
+Restarting it
+-------------------
+
+    >>> b.restart_ff()
+    >>> b.firefox_running
+    True
+
+Taking screenshots
+-------------------------
+You can take screenshots of the running firefox, too::
+
+    >>> f = tempfile.mkdtemp()
+    >>> b.screenshot(f, "screenshot")
+    >>> 'PNG' in open(os.path.join(f, "screenshot.png")).read()
+    True
+
+
+Executing arbitrary JavaScript in the current context
+-------------------------------------------------------------
+
+    >>> b.open(url)
+    >>> b.url
+    'http://localhost:.../print_request'
+    >>> b.exec_contentjs('content.location')
+    'http://localhost:.../print_request \xe2\x80\x94 {host: "localhost:...", hostname: "localhost", port: "..."}'
+
+
+Configuration settings
+--------------------------------------
+
+You can play with some keys in the section ``zc.testbrowser`` if you provide a configuration file::
+
+    - firefox-host
+          mozrepl host
+    - firefox-port
+          mozrepl-port
+    - firefox
+          firefox binary to use
+
+Cleaning
+----------------
+
+    >>> b.stop_ff()
+    >>> remove(b.firefox_profile.profile)
+    >>> remove(f)
+

Modified: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/screen-shots.txt
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/screen-shots.txt	2009-04-23 04:59:07 UTC (rev 99416)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/screen-shots.txt	2009-04-28 20:13:37 UTC (rev 99565)
@@ -1,4 +1,13 @@
+Taking screenshots
+----------------------------
+
     >>> from zc.testbrowser.real import Browser
-    >>> browser = Browser()
-    >>> browser.open('http://localhost:%s/index.html' % TEST_PORT)
-    >>> browser.execute('tb_take_screen_shot("/tmp/1.png")')
+    >>> import tempfile
+    >>> f = tempfile.mkdtemp()
+    >>> browser = Browser('http://localhost:%s/print_request' % TEST_PORT)
+    >>> browser.screenshot(f)
+    >>> browser.stop_ff()
+    >>> 'PNG' in open(os.path.join(f, "screenshot.png")).read()
+    True
+
+

Modified: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/tests.py
===================================================================
--- zc.testbrowser/trunk/src/zc/testbrowser/tests.py	2009-04-23 04:59:07 UTC (rev 99416)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/tests.py	2009-04-28 20:13:37 UTC (rev 99565)
@@ -24,16 +24,28 @@
 import random
 import re
 import string
+import socket
+import tempfile
 import threading
 import unittest
 import urllib
 import urllib2
 import zc.testbrowser.browser
+import zc.testbrowser.proxy
 import zc.testbrowser.real
+import zc.testbrowser.utils
+import socket
 
+# will be gloabals in test.
+from  zc.buildout.testing import cat, ls, remove
+from zc.testbrowser.real import Browser as RealBrowser
+from zc.testbrowser.real import MozReplProfile
+from zc.testbrowser.proxy  import BaseAnonymousBrowser, FirefoxBrowser, AnonymousBrowser, FF2_USERAGENT
 
+
 web_server_base_path = os.path.join(os.path.split(__file__)[0], 'ftests')
 
+#socket.setdefaulttimeout(20)
 
 class TestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 
@@ -44,6 +56,21 @@
         return 'Mon, 17 Sep 2007 10:05:42 GMT'
 
     def do_GET(self):
+        if self.path.endswith('/print_request'):
+            self.end_headers()
+            self.send_response(200, '\n\n\html><body>'
+                               '<pre>%s</pre>'
+                               '</body></html>' % self.headers)
+            return
+
+        if self.path == '/':
+            self.end_headers()
+            self.send_response(200,
+                               '\n\n<html><body>'
+                               '<pre>\nYou are on r00t\n</pre>'
+                               '</body></html>')
+            return
+
         if self.path.endswith('robots.txt'):
             self.send_response(404)
             self.send_header('Connection', 'close')
@@ -52,6 +79,7 @@
         try:
             f = open(web_server_base_path + self.path)
         except IOError:
+
             self.send_response(500)
             self.send_header('Connection', 'close')
             return
@@ -264,8 +292,8 @@
     >>> browser.open('''\
     ... <html><body>
     ...   <form action="." method="post" enctype="multipart/form-data">
-    ...      <input type="submit" name="submit_me" value=" GOOD " />
-    ...      <input type="submit" name="submit_me" value=" BAD " />
+    ...      <input type="submit" name="submit_me" value=" GOOD" />
+    ...      <input type="submit" name="submit_me" value=" BAD" />
     ...   </form></body></html>
     ... ''') # doctest: +ELLIPSIS
     GET / HTTP/1.1
@@ -273,7 +301,7 @@
     >>> browser.getControl('BAD')
     <SubmitControl name='submit_me' type='submit'>
     >>> browser.getControl('BAD').value
-    ' BAD '
+    ' BAD'
     >>> browser.getControl('BAD').click() # doctest: +REPORT_NDIFF
     POST / HTTP/1.1
     Content-length: 176
@@ -285,7 +313,7 @@
     -----------------------------100167997466992641913031254
     Content-disposition: form-data; name="submit_me"
     <BLANKLINE>
-     BAD 
+     BAD
     -----------------------------100167997466992641913031254--
     <BLANKLINE>
 
@@ -432,13 +460,12 @@
     ])
 
 def serve_requests(server):
-    global server_stop
-    while not server_stop:
+    server.stop = False
+    while not server.stop:
         server.handle_request()
-    server.server_close()
 
 def setUpServer(test):
-    global server_stop
+    global server_stop, ports
     server_stop = False
     port = random.randint(20000, 30000)
     test.globs['TEST_PORT'] = port
@@ -447,10 +474,30 @@
     thread.setDaemon(True)
     thread.start()
     test.globs['web_server_thread'] = thread
+    test.globs['server'] = server
+    test.globs['tempfile'] = tempfile
+    test.globs['cat'] = cat
+    test.globs['os'] = os
 
+def setUpProxyServers(test):
+    server_stop = False
+    test.globs['proxies'] = []
+    test.globs['web_server_threads'] = []
+    test.globs['servers'] = []
+    for i in range(4):
+        port = random.randint(20000, 30000)
+        test.globs['TEST_PORT'] = port
+        test.globs['proxies'].append('http://localhost:%s' % port)
+        server = BaseHTTPServer.HTTPServer(('localhost', port), TestHandler)
+        thread = threading.Thread(target=serve_requests, args=[server])
+        thread.setDaemon(True)
+        thread.start()
+        test.globs['web_server_threads'].append(thread)
+        test.globs['servers'].append(server)
+    test.globs.update(globals())
+
 def tearDownServer(test):
-    global server_stop
-    server_stop = True
+    test.globs['server'].stop = True
     # make a request, so the last call to `handle_one_request` will return
     try:
         urllib.urlretrieve('http://localhost:%d/' % test.globs['TEST_PORT'])
@@ -458,6 +505,18 @@
         pass # it's ok, server is already dead
     test.globs['web_server_thread'].join()
 
+def tearDownProxyServers(test):
+    for server in test.globs['servers']:
+        server.stop = True
+        # make a request, so the last call to `handle_one_request` will return
+        try:
+            urllib.urlretrieve('http://localhost:%d/' % server.server_port)
+        except IOError:
+            pass # it's ok, server is already dead
+    if 'web_server_threads' in test.globs:
+        for t in test.globs['web_server_threads']:
+            t.join()
+
 def setUpReal(test):
     test.globs['Browser'] = zc.testbrowser.real.Browser
     setUpServer(test)
@@ -486,14 +545,28 @@
         checker=checker, setUp=setUpReal, tearDown=tearDownServer)
     real_readme.level = 3
 
+    proxy = doctest.DocFileSuite('proxy.txt', optionflags=flags,
+                                 setUp=setUpProxyServers, tearDown=tearDownProxyServers)
+
+    real = doctest.DocFileSuite('real.txt', optionflags=flags,
+                                 setUp=setUpProxyServers, tearDown=tearDownProxyServers)
+
+    this_file = doctest.DocTestSuite(checker=checker)
+
     screen_shots = doctest.DocFileSuite('screen-shots.txt', optionflags=flags,
         setUp=setUpServer, tearDown=tearDownServer)
-    screen_shots.level = 3
 
-    this_file = doctest.DocTestSuite(checker=checker)
+    return unittest.TestSuite(
+        (
+            this_file,
+            readme,
+            real_readme,
+            headers,
+            performance,
+            real,
+            proxy,
+        )
+    )
 
-    return unittest.TestSuite((this_file, readme, real_readme, headers,
-        performance, screen_shots))
-
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')

Added: zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/utils.py
===================================================================
--- zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/utils.py	                        (rev 0)
+++ zc.testbrowser/branches/fastreal_screenshots_mozrunner_proxys/src/zc/testbrowser/utils.py	2009-04-28 20:13:37 UTC (rev 99565)
@@ -0,0 +1,63 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+__docformat__ = "reStructuredText"
+
+import ConfigParser
+import logging
+import os
+
+__CONFIG_SECTION__ = 'zc.testbrowser'
+__CONFIGFILE__ = os.path.expanduser('~/.zc.testbrowser.cfg')
+
+def get_ztb_config(config=None):
+    """If config is a str, just try to read the __CONFIG_SECTION__
+    in config and return it.
+    Ensure also that config is a dict.
+    """
+    if not config:
+        config = ''
+    if isinstance(config, str):
+        configreader = ConfigParser.ConfigParser()
+        # only generate a config file if we want to use proxies
+        if not config:
+            config = __CONFIGFILE__
+        if config and not os.path.exists(config):
+            logging.getLogger(__name__).debug('Generating config file for '
+                                              'storing your %s informaton '
+                                              'in %s.' % (config, __name__))
+            configreader.write(config)
+            configreader.add_section(__CONFIG_SECTION__)
+            configreader.set(__CONFIG_SECTION__, 'proxies', '')
+            configreader.write(open(config, 'w'))
+        configreader.read(config)
+        config = configreader._sections.get(__CONFIG_SECTION__, {})
+    assert isinstance(config, dict)
+    return config
+
+def which(program, environ=None, key = 'PATH', split = ':'):
+    if not environ:
+        environ = os.environ
+    PATH=environ.get(key, '').split(split)
+    fp = None
+    if '/' in program:
+        fp = os.path.abspath(program)
+    if not fp:
+        for entry in PATH:
+            fp = os.path.abspath(os.path.join(entry, program))
+            if os.path.exists(fp):
+                break
+    if os.path.exists(fp):
+        return fp
+    raise IOError('Program not fond: %s in %s ' % (program, PATH))
+



More information about the Checkins mailing list