<commit_before># -*- coding: utf-8 -*-

# Copyright 2015 2016 Juca Crispim <juca@poraodojuca.net>

# This file is part of toxicbuild.

# toxicbuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# toxicbuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with toxicbuild. If not, see <http://www.gnu.org/licenses/>.

import asyncio
from datetime import datetime, timezone, timedelta
import importlib
import logging
import os
from subprocess import PIPE
import sys
import time
import bcrypt
from toxicbuild.core.exceptions import ExecCmdError, ConfigError

@asyncio.coroutine
def exec_cmd(cmd, cwd, timeout=3600, **envvars):
    """ Executes a shell command. Raises with stderr if return code > 0
    :param cmd: command to run.
    :param cwd: Directory to execute the command.
    :param timeout: How long we should wait for a command complete. Default
      is 3600.
    :param envvars: Environment variables to be used in the command.
    """

    envvars = _get_envvars(envvars)

    # the thing here is that in not win operating systems
    # we redirect stderr to stdout so we can see the whole
    # output of the command.
    if not sys.platform.startswith('win32'):
        cmd = ' '.join([cmd, '2>&1'])

    proc = yield from asyncio.create_subprocess_shell(
        cmd, stdout=PIPE, stderr=PIPE, cwd=cwd, env=envvars)

    stdout, stderr = yield from asyncio.wait_for(
        proc.communicate(), timeout)

    output = stdout.decode().strip()
    if int(proc.returncode) > 0:
        raise ExecCmdError(output)

    return output

def _get_envvars(envvars):
    """Returns environment variables to be used in shell. Does the
    interpolation of values using the current values from the envvar
    and the values passed as parameters. """

    newvars = {}
    for var, value in envvars.items():
        if var in value:
            current = os.environ.get(var, '')
            value = value.replace(var, current)

        newvars[var] = value

    newvars['LANG'] = os.environ.get('LANG')
    return newvars

def load_module_from_file(filename):
    """ Load a module from a source file
    :param filename: full path for file to be loaded.
    """
    fname, extension = filename.rsplit('.', 1)
    fname = fname.rsplit(os.sep, 1)[-1]
    source_file = importlib.machinery.SourceFileLoader(fname, filename)

    try:
        module = source_file.load_module()
    except FileNotFoundError:
        err_msg = 'Config file "%s" does not exist!' % (filename)
        raise FileNotFoundError(err_msg)
    except Exception as e:
        err_msg = 'There is something wrong with your file. '
        err_msg += 'The original exception was:\n{}'.format(e.args[0])
        raise ConfigError(err_msg)

    return module

def log(msg, level='info'):
    log = getattr(logging, level)
    dt = now().strftime('%Y-%m-%d %H:%M:%S')
    msg = ' {} - {}'.format(dt, msg)
    log(msg)

class LoggerMixin:

    """A simple mixin to use log on a class."""

    def log(self, msg, level='info'):
        """Appends the class name before the log message. """

        log('[{}] {} '.format(type(self).__name__, msg), level)

def datetime2string(dt, dtformat='%a %b %d %H:%M:%S %Y %z'):
    """Transforms a datetime object into a formated string.

    :param dt: The datetime object.
    :param dtformat: The format to use."""

    return datetime.strftime(dt, dtformat)

def string2datetime(dtstr, dtformat='%a %b %d %H:%M:%S %Y %z'):
    """Transforns a string into a datetime object acording to ``dtformat``.

    :param dtstr: The string containing the formated date.
    :param dtformat: The format of the formated date.
    """
    return datetime.strptime(dtstr, dtformat)

def utc2localtime(utcdatetime):
    """Transforms a utc datetime object into a datetime object
    in local time.

    :param utcdatetime: A datetime object"""

    off = time.localtime().tm_gmtoff
    td = timedelta(seconds=off)
    tz = timezone(td)
    local = utcdatetime + td
    localtime = datetime(local.year, local.month, local.day,
                         local.hour, local.minute, local.second,
                         local.microsecond,
                         tzinfo=tz)
    return localtime

def localtime2utc(localdatetime):
    """Transforms a local datetime object into a datetime object
    in utc time.

    :param localdatetime: A datetime object."""
    off = time.localtime().tm_gmtoff
    td = timedelta(seconds=off)
    utc = localdatetime - td
    utctz = timezone(timedelta(seconds=0))
    utctime = datetime(utc.year, utc.month, utc.day,
                       utc.hour, utc.minute, utc.second,
                       utc.microsecond,
                       tzinfo=utctz)
    return utctime

def now():
    """ Returns the localtime with timezone info. """

    off = time.localtime().tm_gmtoff
    tz = timezone(timedelta(seconds=off))
    return datetime.now(tz=tz)

def get_toxicbuildconf(directory):
    """Returns the toxicbuild.conf module.

    :param directory: Directory to look for toxicbuild.conf"""

    configfile = os.path.join(directory, 'toxicbuild.conf')
    return load_module_from_file(configfile)

def list_builders_from_config(confmodule, branch, slave):
    builders = [b['name'] for b in confmodule.BUILDERS
                if (b.get('branch') == branch or b.get('branch')is None) and
                (b.get('slave') == slave.name or b.get('slave') is None)]
    return builders

# inherit_docs thanks to Raymond Hettinger on stackoverflow
# stackoverflow.com/questions/8100166/inheriting-methods-docstrings-in-python
def inherit_docs(cls):
    """ Inherit docstrings from base classes' methods.
    Can be used as a decorator

    :param cls: Class that will inherit docstrings from its parents.
    """
    for name, func in vars(cls).items():
        if not func.__doc__:
            for parent in cls.__bases__:
                try:
                    parfunc = getattr(parent, name)
                except:
                    continue
                if parfunc and getattr(parfunc, '__doc__', None):
                    func.__doc__ = parfunc.__doc__
                    break
    return cls

@asyncio.coroutine
def read_stream(reader):
    """ Reads the input stream. First reads the bytes until the first \n.
    These first bytes are the length of the full message.

    :param reader: An instance of :class:`asyncio.StreamReader`
    """

    data = yield from reader.read(1)
    if not data or data == b'\n':
        raw_data = b''
    else:
        char = None
        while char != b'\n' and char != b'':
            char = yield from reader.read(1)
            data += char

        len_data = int(data)

        if len_data <= 1024:
            raw_data = yield from reader.read(len_data)
        else:
            raw_data = yield from reader.read(1024)
            while len(raw_data) < len_data:
                left = len_data - len(raw_data)
                next_chunk = left if left < 1024 else 1024
                raw_data += yield from reader.read(next_chunk)

    return raw_data

@asyncio.coroutine
def write_stream(writer, data):
    """ Writes ``data`` to output. Encodes data to utf-8 and prepend the
    lenth of the data before sending it.

    :param writer: An instance of asyncio.StreamWriter
    :param data: String data to be sent.
    """

    data = data.encode('utf-8')
    data = '{}\n'.format(len(data)).encode('utf-8') + data
    writer.write(data)
    yield from writer.drain()

def bcrypt_string(string, salt):
    encoding = sys.getdefaultencoding()
    if isinstance(salt, str):
        salt = salt.encode(encoding)
    encrypted = bcrypt.hashpw(string.encode(encoding), salt)
    return encrypted.decode()

class changedir(object):

    def __init__(self, path):
        self.old_dir = os.getcwd()
        self.current_dir = path

    def __enter__(self):
        os.chdir(self.current_dir)

    def __exit__(self, *a, **kw):
        os.chdir(self.old_dir)

# Sorry, but not willing to test  a daemonizer.
def daemonize(call, cargs, ckwargs, stdout, stderr,
              workdir, pidfile):  # pragma: no cover
    """ Run a callable as a daemon

    :param call: a callable.
    :param cargs: args to ``call``.
    :param ckwargs: kwargs to ``call``.
    :param stdout: daemon's stdout.
    :param stderr: daemon's stderr.
    :param workdir: daemon's workdir
    :param pidfile: pidfile's path
    """
    _create_daemon(stdout, stderr, workdir)
    pid = os.getpid()
    with open(pidfile, 'w') as f:
        f.write(str(pid))

    call(*cargs, **ckwargs)

def _create_daemon(stdout, stderr, workdir):  # pragma: no cover
    _fork_off_and_die()
    os.setsid()
    _fork_off_and_die()
    os.umask(0)
    os.chdir(workdir)
    _redirect_file_descriptors(stdout, stderr)

def _fork_off_and_die():  # pragma: no cover
    pid = os.fork()
    if pid != 0:
        sys.exit(0)

def _redirect_file_descriptors(stdout, stderr):  # pragma: no cover
    for fd in sys.stdout, sys.stderr:
        fd.flush()

    sys.stdout = open(stdout, 'a', 1)
    sys.stderr = open(stderr, 'a', 1)
<commit_msg>correcting $LANG envvar handling<commit_after># -*- coding: utf-8 -*-

# Copyright 2015 2016 Juca Crispim <juca@poraodojuca.net>

# This file is part of toxicbuild.

# toxicbuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# toxicbuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with toxicbuild. If not, see <http://www.gnu.org/licenses/>.

import asyncio
from datetime import datetime, timezone, timedelta
import importlib
import logging
import os
from subprocess import PIPE
import sys
import time
import bcrypt
from toxicbuild.core.exceptions import ExecCmdError, ConfigError

@asyncio.coroutine
def exec_cmd(cmd, cwd, timeout=3600, **envvars):
    """ Executes a shell command. Raises with stderr if return code > 0
    :param cmd: command to run.
    :param cwd: Directory to execute the command.
    :param timeout: How long we should wait for a command complete. Default
      is 3600.
    :param envvars: Environment variables to be used in the command.
    """

    envvars = _get_envvars(envvars)

    # the thing here is that in not win operating systems
    # we redirect stderr to stdout so we can see the whole
    # output of the command.
    if not sys.platform.startswith('win32'):
        cmd = ' '.join([cmd, '2>&1'])

    proc = yield from asyncio.create_subprocess_shell(
        cmd, stdout=PIPE, stderr=PIPE, cwd=cwd, env=envvars)

    stdout, stderr = yield from asyncio.wait_for(
        proc.communicate(), timeout)

    output = stdout.decode().strip()
    if int(proc.returncode) > 0:
        raise ExecCmdError(output)

    return output

def _get_envvars(envvars):
    """Returns environment variables to be used in shell. Does the
    interpolation of values using the current values from the envvar
    and the values passed as parameters. """

    newvars = {}
    for var, value in envvars.items():
        if var in value:
            current = os.environ.get(var, '')
            value = value.replace(var, current)

        newvars[var] = value

    newvars['LANG'] = os.environ.get('LANG', '')
    return newvars

def load_module_from_file(filename):
    """ Load a module from a source file
    :param filename: full path for file to be loaded.
    """
    fname, extension = filename.rsplit('.', 1)
    fname = fname.rsplit(os.sep, 1)[-1]
    source_file = importlib.machinery.SourceFileLoader(fname, filename)

    try:
        module = source_file.load_module()
    except FileNotFoundError:
        err_msg = 'Config file "%s" does not exist!' % (filename)
        raise FileNotFoundError(err_msg)
    except Exception as e:
        err_msg = 'There is something wrong with your file. '
        err_msg += 'The original exception was:\n{}'.format(e.args[0])
        raise ConfigError(err_msg)

    return module

def log(msg, level='info'):
    log = getattr(logging, level)
    dt = now().strftime('%Y-%m-%d %H:%M:%S')
    msg = ' {} - {}'.format(dt, msg)
    log(msg)

class LoggerMixin:

    """A simple mixin to use log on a class."""

    def log(self, msg, level='info'):
        """Appends the class name before the log message. """

        log('[{}] {} '.format(type(self).__name__, msg), level)

def datetime2string(dt, dtformat='%a %b %d %H:%M:%S %Y %z'):
    """Transforms a datetime object into a formated string.

    :param dt: The datetime object.
    :param dtformat: The format to use."""

    return datetime.strftime(dt, dtformat)

def string2datetime(dtstr, dtformat='%a %b %d %H:%M:%S %Y %z'):
    """Transforns a string into a datetime object acording to ``dtformat``.

    :param dtstr: The string containing the formated date.
    :param dtformat: The format of the formated date.
    """
    return datetime.strptime(dtstr, dtformat)

def utc2localtime(utcdatetime):
    """Transforms a utc datetime object into a datetime object
    in local time.

    :param utcdatetime: A datetime object"""

    off = time.localtime().tm_gmtoff
    td = timedelta(seconds=off)
    tz = timezone(td)
    local = utcdatetime + td
    localtime = datetime(local.year, local.month, local.day,
                         local.hour, local.minute, local.second,
                         local.microsecond,
                         tzinfo=tz)
    return localtime

def localtime2utc(localdatetime):
    """Transforms a local datetime object into a datetime object
    in utc time.

    :param localdatetime: A datetime object."""
    off = time.localtime().tm_gmtoff
    td = timedelta(seconds=off)
    utc = localdatetime - td
    utctz = timezone(timedelta(seconds=0))
    utctime = datetime(utc.year, utc.month, utc.day,
                       utc.hour, utc.minute, utc.second,
                       utc.microsecond,
                       tzinfo=utctz)
    return utctime

def now():
    """ Returns the localtime with timezone info. """

    off = time.localtime().tm_gmtoff
    tz = timezone(timedelta(seconds=off))
    return datetime.now(tz=tz)

def get_toxicbuildconf(directory):
    """Returns the toxicbuild.conf module.

    :param directory: Directory to look for toxicbuild.conf"""

    configfile = os.path.join(directory, 'toxicbuild.conf')
    return load_module_from_file(configfile)

def list_builders_from_config(confmodule, branch, slave):
    builders = [b['name'] for b in confmodule.BUILDERS
                if (b.get('branch') == branch or b.get('branch')is None) and
                (b.get('slave') == slave.name or b.get('slave') is None)]
    return builders

# inherit_docs thanks to Raymond Hettinger on stackoverflow
# stackoverflow.com/questions/8100166/inheriting-methods-docstrings-in-python
def inherit_docs(cls):
    """ Inherit docstrings from base classes' methods.
    Can be used as a decorator

    :param cls: Class that will inherit docstrings from its parents.
    """
    for name, func in vars(cls).items():
        if not func.__doc__:
            for parent in cls.__bases__:
                try:
                    parfunc = getattr(parent, name)
                except:
                    continue
                if parfunc and getattr(parfunc, '__doc__', None):
                    func.__doc__ = parfunc.__doc__
                    break
    return cls

@asyncio.coroutine
def read_stream(reader):
    """ Reads the input stream. First reads the bytes until the first \n.
    These first bytes are the length of the full message.

    :param reader: An instance of :class:`asyncio.StreamReader`
    """

    data = yield from reader.read(1)
    if not data or data == b'\n':
        raw_data = b''
    else:
        char = None
        while char != b'\n' and char != b'':
            char = yield from reader.read(1)
            data += char

        len_data = int(data)

        if len_data <= 1024:
            raw_data = yield from reader.read(len_data)
        else:
            raw_data = yield from reader.read(1024)
            while len(raw_data) < len_data:
                left = len_data - len(raw_data)
                next_chunk = left if left < 1024 else 1024
                raw_data += yield from reader.read(next_chunk)

    return raw_data

@asyncio.coroutine
def write_stream(writer, data):
    """ Writes ``data`` to output. Encodes data to utf-8 and prepend the
    lenth of the data before sending it.

    :param writer: An instance of asyncio.StreamWriter
    :param data: String data to be sent.
    """

    data = data.encode('utf-8')
    data = '{}\n'.format(len(data)).encode('utf-8') + data
    writer.write(data)
    yield from writer.drain()

def bcrypt_string(string, salt):
    encoding = sys.getdefaultencoding()
    if isinstance(salt, str):
        salt = salt.encode(encoding)
    encrypted = bcrypt.hashpw(string.encode(encoding), salt)
    return encrypted.decode()

class changedir(object):

    def __init__(self, path):
        self.old_dir = os.getcwd()
        self.current_dir = path

    def __enter__(self):
        os.chdir(self.current_dir)

    def __exit__(self, *a, **kw):
        os.chdir(self.old_dir)

# Sorry, but not willing to test  a daemonizer.
def daemonize(call, cargs, ckwargs, stdout, stderr,
              workdir, pidfile):  # pragma: no cover
    """ Run a callable as a daemon

    :param call: a callable.
    :param cargs: args to ``call``.
    :param ckwargs: kwargs to ``call``.
    :param stdout: daemon's stdout.
    :param stderr: daemon's stderr.
    :param workdir: daemon's workdir
    :param pidfile: pidfile's path
    """
    _create_daemon(stdout, stderr, workdir)
    pid = os.getpid()
    with open(pidfile, 'w') as f:
        f.write(str(pid))

    call(*cargs, **ckwargs)

def _create_daemon(stdout, stderr, workdir):  # pragma: no cover
    _fork_off_and_die()
    os.setsid()
    _fork_off_and_die()
    os.umask(0)
    os.chdir(workdir)
    _redirect_file_descriptors(stdout, stderr)

def _fork_off_and_die():  # pragma: no cover
    pid = os.fork()
    if pid != 0:
        sys.exit(0)

def _redirect_file_descriptors(stdout, stderr):  # pragma: no cover
    for fd in sys.stdout, sys.stderr:
        fd.flush()

    sys.stdout = open(stdout, 'a', 1)
    sys.stderr = open(stderr, 'a', 1)
<|endoftext|>


<commit_before># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2004 Edgewall Software
# Copyright (C) 2004 Christopher Lenz <cmlenz@gmx.de>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

import re, threading
import auth, core, Environment, Href, Session, Wiki
from util import TracError
from mod_python import apache, util

content_type_re = re.compile(r'^Content-Type$', re.IGNORECASE)

class ModPythonRequest(core.Request):

    def __init__(self, req):
        self.req = req

    def init_request(self):
        core.Request.init_request(self)

        # TODO This will need proxy host name support (see #437 and [581])
        host = self.req.hostname
        port = self.req.connection.local_addr[1]
        path = re.sub('%s$' % re.escape(self.req.path_info), '', self.req.uri)
        if port == 80:
            self.base_url = 'http://%s%s' % (host, path)
        elif port == 443:
            self.base_url = 'https://%s%s' % (host, path)
        else:
            self.base_url = 'http://%s:%d%s' % (host, port, path)

        self.cgi_location = path
        self.remote_addr = self.req.connection.remote_ip
        self.remote_user = self.req.user
        self.command = self.req.method

        if self.req.headers_in.has_key('Cookie'):
            self.incookie.load(self.req.headers_in['Cookie'])

        self.hdf.setValue('HTTP.Host', self.req.hostname)

    def read(self, len):
        return self.req.read(len)

    def write(self, data):
        self.req.write(data)

    def get_header(self, name):
        return self.req.headers_in.get(name)

    def send_response(self, code):
        self.req.status = code

    def send_header(self, name, value):
        if content_type_re.match(name):
            self.req.content_type = value
        else:
            self.req.headers_out.add(name, str(value))

    def end_headers(self):
        pass

class TracFieldStorage(util.FieldStorage):
    """
    FieldStorage class with an added get function.
    """
    def get(self, key, default=''):
        return util.FieldStorage.get(self, key, default)

def init_env(req):

    options = req.req.get_options()
    if not options.has_key('TracEnv'):
        raise EnvironmentError, \
            'Missing PythonOption "TracEnv". Trac requires this option '\
            'to point to a valid Trac Environment.'
    env_path = options['TracEnv']

    env = Environment.Environment(env_path)
    version = env.get_version()
    if version < Environment.db_version:
        raise TracError('The Trac environment needs to be upgraded. '
                        'Run "trac-admin %s upgrade"' % env_path)
    elif version > Environment.db_version:
        raise TracError('Unknown Trac Environment version (%d).' % version)

    env.href = Href.Href(req.cgi_location)
    env.abs_href = Href.Href(req.base_url)

    # Let the wiki module build a dictionary of all page names
    database = env.get_db_cnx()
    Wiki.populate_page_dict(database, env)

    return env

projects = {}
projects_lock = threading.Lock()

def handler(req):
    global projects, projects_lock

    mpr = ModPythonRequest(req)
    mpr.init_request()

    projects_lock.acquire()
    env = projects.get(req.filename, None)
    if not env:
        env = init_env(mpr)
        projects[req.filename] = env
    projects_lock.release()

    args = TracFieldStorage(req)
    core.parse_path_info(args, req.path_info)

    req.content_type = 'text/html'
    try:
        core.dispatch_request(req.path_info, args, mpr, env)
    except Exception, e:
        core.send_pretty_error(e, env, mpr)

    return apache.OK
<commit_msg>Added a "TracEnvParentDir" option.<commit_after># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2004 Edgewall Software
# Copyright (C) 2004 Christopher Lenz <cmlenz@gmx.de>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

import os
import re, threading
import auth, core, Environment, Href, Session, Wiki
from util import TracError
from mod_python import apache, util

class ModPythonRequest(core.Request):

    def __init__(self, req):
        self.req = req

    def init_request(self):
        core.Request.init_request(self)
        options = self.req.get_options()

        if options.has_key('TracEnvParentDir') and self.req.path_info:
            # We have to remove one path element from path_info when we're
            # using TracEnvParentDir
            self.path_info = re.sub('/[^/]+', '', self.req.path_info, 1)
        else:
            self.path_info = self.req.path_info

        if len(self.path_info):
            self.cgi_location = self.req.uri[:-len(self.path_info)]
        else:
            self.cgi_location = self.req.uri

        if len(self.req.path_info):
            self.idx_location = self.req.uri[:-len(self.req.path_info)]
        else:
            self.idx_location = self.req.uri

        # TODO This will need proxy host name support (see #437 and [581])
        host = self.req.hostname
        port = self.req.connection.local_addr[1]
        if port == 80:
            self.base_url = 'http://%s%s' % (host, self.cgi_location)
        elif port == 443:
            self.base_url = 'https://%s%s' % (host, self.cgi_location)
        else:
            self.base_url = 'http://%s:%d%s' % (host, port, cgi_location)

        self.remote_addr = self.req.connection.remote_ip
        self.remote_user = self.req.user
        self.command = self.req.method

        if self.req.headers_in.has_key('Cookie'):
            self.incookie.load(self.req.headers_in['Cookie'])

        self.hdf.setValue('HTTP.Host', self.req.hostname)

    def read(self, len):
        return self.req.read(len)

    def write(self, data):
        self.req.write(data)

    def get_header(self, name):
        return self.req.headers_in.get(name)

    def send_response(self, code):
        self.req.status = code

    def send_header(self, name, value):
        if name.lower() == 'content-type':
            self.req.content_type = value
        else:
            self.req.headers_out.add(name, str(value))

    def end_headers(self):
        pass

class TracFieldStorage(util.FieldStorage):
    """
    FieldStorage class with an added get function.
    """
    def get(self, key, default=''):
        return util.FieldStorage.get(self, key, default)

def send_project_index(req, mpr, dir):
    req.content_type = 'text/html'
    req.write('<title>Available Projects</title>')
    req.write('
Available Projects

')
    for project in os.listdir(dir):
        req.write('
%s
' % (mpr.idx_location + '/' + project,
                                                    project))
    req.write('
')

def open_environment(env_path, mpr):
    env = Environment.Environment(env_path)
    version = env.get_version()
    if version < Environment.db_version:
        raise TracError('The Trac environment needs to be upgraded. '
                        'Run "trac-admin %s upgrade"' % env_path)
    elif version > Environment.db_version:
        raise TracError('Unknown Trac Environment version (%d).' % version)

    env.href = Href.Href(mpr.cgi_location)
    env.abs_href = Href.Href(mpr.base_url)
    # Let the wiki module build a dictionary of all page names
    database = env.get_db_cnx()
    Wiki.populate_page_dict(database, env)
    return env

env_cache = {}
env_cache_lock = threading.Lock()

def get_environment(req, mpr):
    global env_cache, env_cache_lock
    options = req.get_options()

    if not options.has_key('TracEnv') and not options.has_key('TracEnvParentDir'):
        raise EnvironmentError, \
              'Missing PythonOption "TracEnv". Trac requires this option '\
              'to point to a valid Trac Environment.'

    if options.has_key('TracEnv'):
        env_path = options['TracEnv']

    elif options.has_key('TracEnvParentDir'):
        env_parent_dir = options['TracEnvParentDir']
        env_name = mpr.cgi_location.split('/')[-1]
        env_path = os.path.join(env_parent_dir, env_name)
        if len(env_name) == 0 or not os.path.exists(env_path):
            send_project_index(req, mpr, env_parent_dir)
            return None

    env_cache_lock.acquire()
    if not env_path in env_cache:
        env_cache[env_path] = open_environment(env_path, mpr)
    env = env_cache[env_path]
    env_cache_lock.release()
    return env

def handler(req):
    global projects, projects_lock

    mpr = ModPythonRequest(req)
    mpr.init_request()

    env = get_environment(req, mpr)
    if not env:
        return apache.OK

    args = TracFieldStorage(req)
    core.parse_path_info(args, mpr.path_info)

    req.content_type = 'text/html'
    try:
        core.dispatch_request(mpr.path_info, args, mpr, env)
    except Exception, e:
        core.send_pretty_error(e, env, mpr)
    return apache.OK
<|endoftext|>


<commit_before>import os
import shutil
import tempfile
import unittest

from trac.attachment import Attachment
from trac.wiki.tests import formatter

SEARCH_TEST_CASES = """
============================== search: link resolver
search:foo
search:"foo bar"
[search:bar Bar]
[search:bar]
[search:]
------------------------------

search:foo
search:"foo bar"
Bar
bar
search

------------------------------
============================== search: link resolver with query arguments
search:?q=foo&wiki=on
search:"?q=foo bar&wiki=on"
[search:?q=bar&ticket=on Bar in Tickets]
------------------------------

search:?q=foo&wiki=on
search:"?q=foo bar&wiki=on"
Bar in Tickets

------------------------------
"""

ATTACHMENT_TEST_CASES = """
============================== attachment: link resolver (deprecated)
attachment:wiki:WikiStart:file.txt (deprecated)
attachment:ticket:123:file.txt (deprecated)
[attachment:wiki:WikiStart:file.txt file.txt] (deprecated)
[attachment:ticket:123:file.txt] (deprecated)
------------------------------

attachment:wiki:WikiStart:file.txt  (deprecated)
attachment:ticket:123:file.txt  (deprecated)
file.txt  (deprecated)
ticket:123:file.txt  (deprecated)

------------------------------
============================== attachment: "foreign" links
attachment:file.txt:wiki:WikiStart
attachment:file.txt:ticket:123
[attachment:file.txt:wiki:WikiStart file.txt]
[attachment:file.txt:ticket:123]
attachment:foo.txt:wiki:SomePage/SubPage
------------------------------

attachment:file.txt:wiki:WikiStart
attachment:file.txt:ticket:123
file.txt
file.txt:ticket:123
attachment:foo.txt:wiki:SomePage/SubPage

------------------------------
============================== attachment: "local" links
attachment:file.txt
[attachment:file.txt that file]
------------------------------

attachment:file.txt
that file

------------------------------
============================== attachment: "missing" links
attachment:foo.txt
[attachment:foo.txt other file]
------------------------------

attachment:foo.txt
other file

------------------------------
============================== attachment: "raw" links
raw-attachment:file.txt
[raw-attachment:file.txt that file]
------------------------------

raw-attachment:file.txt
that file

------------------------------
============================== attachment: raw format as explicit argument
attachment:file.txt?format=raw
[attachment:file.txt?format=raw that file]
------------------------------

attachment:file.txt?format=raw
that file

------------------------------
""" # "

def attachment_setup(tc):
    import trac.ticket.api
    import trac.wiki.api
    tc.env.path = os.path.join(tempfile.gettempdir(), 'trac-tempenv')
    os.mkdir(tc.env.path)
    attachment = Attachment(tc.env, 'wiki', 'WikiStart')
    attachment.insert('file.txt', tempfile.TemporaryFile(), 0)
    attachment = Attachment(tc.env, 'ticket', 123)
    attachment.insert('file.txt', tempfile.TemporaryFile(), 0)
    attachment = Attachment(tc.env, 'wiki', 'SomePage/SubPage')
    attachment.insert('foo.txt', tempfile.TemporaryFile(), 0)

def attachment_teardown(tc):
    shutil.rmtree(tc.env.path)
    tc.env.reset_db()

def suite():
    suite = unittest.TestSuite()
    suite.addTest(formatter.suite(SEARCH_TEST_CASES, file=__file__))
    suite.addTest(formatter.suite(ATTACHMENT_TEST_CASES, file=__file__,
                                  context=('wiki', 'WikiStart'),
                                  setup=attachment_setup,
                                  teardown=attachment_teardown))
    return suite

if __name__ == '__main__':
    unittest.main(defaultTest='suite')

<commit_msg>tests: make it possible to run trac/tests/wikisyntax.py and trac/tests/allwiki.py directly without errors.<commit_after>import os
import shutil
import tempfile
import unittest

from trac.attachment import Attachment
from trac.search.web_ui import SearchModule
from trac.wiki.tests import formatter

SEARCH_TEST_CASES = """
============================== search: link resolver
search:foo
search:"foo bar"
[search:bar Bar]
[search:bar]
[search:]
------------------------------

search:foo
search:"foo bar"
Bar
bar
search

------------------------------
============================== search: link resolver with query arguments
search:?q=foo&wiki=on
search:"?q=foo bar&wiki=on"
[search:?q=bar&ticket=on Bar in Tickets]
------------------------------

search:?q=foo&wiki=on
search:"?q=foo bar&wiki=on"
Bar in Tickets

------------------------------
"""

ATTACHMENT_TEST_CASES = """
============================== attachment: link resolver (deprecated)
attachment:wiki:WikiStart:file.txt (deprecated)
attachment:ticket:123:file.txt (deprecated)
[attachment:wiki:WikiStart:file.txt file.txt] (deprecated)
[attachment:ticket:123:file.txt] (deprecated)
------------------------------

attachment:wiki:WikiStart:file.txt  (deprecated)
attachment:ticket:123:file.txt  (deprecated)
file.txt  (deprecated)
ticket:123:file.txt  (deprecated)

------------------------------
============================== attachment: "foreign" links
attachment:file.txt:wiki:WikiStart
attachment:file.txt:ticket:123
[attachment:file.txt:wiki:WikiStart file.txt]
[attachment:file.txt:ticket:123]
attachment:foo.txt:wiki:SomePage/SubPage
------------------------------

attachment:file.txt:wiki:WikiStart
attachment:file.txt:ticket:123
file.txt
file.txt:ticket:123
attachment:foo.txt:wiki:SomePage/SubPage

------------------------------
============================== attachment: "local" links
attachment:file.txt
[attachment:file.txt that file]
------------------------------

attachment:file.txt
that file

------------------------------
============================== attachment: "missing" links
attachment:foo.txt
[attachment:foo.txt other file]
------------------------------

attachment:foo.txt
other file

------------------------------
============================== attachment: "raw" links
raw-attachment:file.txt
[raw-attachment:file.txt that file]
------------------------------

raw-attachment:file.txt
that file

------------------------------
============================== attachment: raw format as explicit argument
attachment:file.txt?format=raw
[attachment:file.txt?format=raw that file]
------------------------------

attachment:file.txt?format=raw
that file

------------------------------
""" # "

def attachment_setup(tc):
    import trac.ticket.api
    import trac.wiki.api
    tc.env.path = os.path.join(tempfile.gettempdir(), 'trac-tempenv')
    os.mkdir(tc.env.path)
    attachment = Attachment(tc.env, 'wiki', 'WikiStart')
    attachment.insert('file.txt', tempfile.TemporaryFile(), 0)
    attachment = Attachment(tc.env, 'ticket', 123)
    attachment.insert('file.txt', tempfile.TemporaryFile(), 0)
    attachment = Attachment(tc.env, 'wiki', 'SomePage/SubPage')
    attachment.insert('foo.txt', tempfile.TemporaryFile(), 0)

def attachment_teardown(tc):
    shutil.rmtree(tc.env.path)
    tc.env.reset_db()

def suite():
    suite = unittest.TestSuite()
    suite.addTest(formatter.suite(SEARCH_TEST_CASES, file=__file__))
    suite.addTest(formatter.suite(ATTACHMENT_TEST_CASES, file=__file__,
                                  context=('wiki', 'WikiStart'),
                                  setup=attachment_setup,
                                  teardown=attachment_teardown))
    return suite

if __name__ == '__main__':
    unittest.main(defaultTest='suite')

<|endoftext|>


<commit_before>from trac.ticket.api import TicketSystem
from trac.test import EnvironmentStub, Mock

import unittest

class TicketSystemTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub()
        self.ticket_system = TicketSystem(self.env)

    def test_custom_field_text(self):
        self.env.config.set('ticket-custom', 'test', 'text')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', 'Foo bar')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual({'name': 'test', 'type': 'text', 'label': 'Test',
                          'value': 'Foo bar', 'order': 0},
                         fields[0])

    def test_custom_field_select(self):
        self.env.config.set('ticket-custom', 'test', 'select')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', '1')
        self.env.config.set('ticket-custom', 'test.options', 'option1|option2')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test',
                          'value': '1', 'options': ['option1', 'option2'],
                          'order': 0, 'optional': False},
                         fields[0])

    def test_custom_field_textarea(self):
        self.env.config.set('ticket-custom', 'test', 'textarea')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', 'Foo bar')
        self.env.config.set('ticket-custom', 'test.cols', '60')
        self.env.config.set('ticket-custom', 'test.rows', '4')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual({'name': 'test', 'type': 'textarea', 'label': 'Test',
                          'value': 'Foo bar', 'width': 60, 'height': 4,
                          'order': 0},
                         fields[0])

    def test_custom_field_order(self):
        self.env.config.set('ticket-custom', 'test1', 'text')
        self.env.config.set('ticket-custom', 'test1.order', '2')
        self.env.config.set('ticket-custom', 'test2', 'text')
        self.env.config.set('ticket-custom', 'test2.order', '1')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual('test2', fields[0]['name'])
        self.assertEqual('test1', fields[1]['name'])

    def test_available_actions_full_perms(self):
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: 1)
        self.assertEqual(['leave', 'resolve', 'reassign', 'accept'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave', 'resolve', 'reassign'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave', 'resolve', 'reassign'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave', 'reopen'],
                         ts.get_available_actions({'status': 'closed'}, perm))

    def test_available_actions_no_perms(self):
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: 0)
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'closed'}, perm))

    def test_available_actions_create_only(self):
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: x == 'TICKET_CREATE')
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave', 'reopen'],
                         ts.get_available_actions({'status': 'closed'}, perm))

    def test_available_actions_chgprop_only(self):
        # CHGPROP is not enough for changing a ticket's state (#3289)
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: x == 'TICKET_CHGPROP')
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'closed'}, perm))

def suite():
    return unittest.makeSuite(TicketSystemTestCase, 'test')

if __name__ == '__main__':
    unittest.main()
<commit_msg>add test for optional select custom fields (see r3762)<commit_after>from trac.ticket.api import TicketSystem
from trac.test import EnvironmentStub, Mock

import unittest

class TicketSystemTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub()
        self.ticket_system = TicketSystem(self.env)

    def test_custom_field_text(self):
        self.env.config.set('ticket-custom', 'test', 'text')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', 'Foo bar')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual({'name': 'test', 'type': 'text', 'label': 'Test',
                          'value': 'Foo bar', 'order': 0},
                         fields[0])

    def test_custom_field_select(self):
        self.env.config.set('ticket-custom', 'test', 'select')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', '1')
        self.env.config.set('ticket-custom', 'test.options', 'option1|option2')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test',
                          'value': '1', 'options': ['option1', 'option2'],
                          'order': 0, 'optional': False},
                         fields[0])

    def test_custom_field_optional_select(self):
        self.env.config.set('ticket-custom', 'test', 'select')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', '1')
        self.env.config.set('ticket-custom', 'test.options', '|option1|option2')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test',
                          'value': '1', 'options': ['', 'option1', 'option2'],
                          'order': 0, 'optional': True},
                         fields[0])

    def test_custom_field_textarea(self):
        self.env.config.set('ticket-custom', 'test', 'textarea')
        self.env.config.set('ticket-custom', 'test.label', 'Test')
        self.env.config.set('ticket-custom', 'test.value', 'Foo bar')
        self.env.config.set('ticket-custom', 'test.cols', '60')
        self.env.config.set('ticket-custom', 'test.rows', '4')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual({'name': 'test', 'type': 'textarea', 'label': 'Test',
                          'value': 'Foo bar', 'width': 60, 'height': 4,
                          'order': 0},
                         fields[0])

    def test_custom_field_order(self):
        self.env.config.set('ticket-custom', 'test1', 'text')
        self.env.config.set('ticket-custom', 'test1.order', '2')
        self.env.config.set('ticket-custom', 'test2', 'text')
        self.env.config.set('ticket-custom', 'test2.order', '1')
        fields = TicketSystem(self.env).get_custom_fields()
        self.assertEqual('test2', fields[0]['name'])
        self.assertEqual('test1', fields[1]['name'])

    def test_available_actions_full_perms(self):
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: 1)
        self.assertEqual(['leave', 'resolve', 'reassign', 'accept'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave', 'resolve', 'reassign'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave', 'resolve', 'reassign'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave', 'reopen'],
                         ts.get_available_actions({'status': 'closed'}, perm))

    def test_available_actions_no_perms(self):
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: 0)
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'closed'}, perm))

    def test_available_actions_create_only(self):
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: x == 'TICKET_CREATE')
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave', 'reopen'],
                         ts.get_available_actions({'status': 'closed'}, perm))

    def test_available_actions_chgprop_only(self):
        # CHGPROP is not enough for changing a ticket's state (#3289)
        ts = TicketSystem(self.env)
        perm = Mock(has_permission=lambda x: x == 'TICKET_CHGPROP')
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'new'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'assigned'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'reopened'}, perm))
        self.assertEqual(['leave'],
                         ts.get_available_actions({'status': 'closed'}, perm))

def suite():
    return unittest.makeSuite(TicketSystemTestCase, 'test')

if __name__ == '__main__':
    unittest.main()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Edgewall Software
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.

"""Utilities for text translation with gettext."""

import re
import sys
try:
    import threading
except ImportError:
    import dummy_threading as threading

import pkg_resources

from genshi.builder import tag

__all__ = ['gettext', 'ngettext', 'gettext_noop', 'ngettext_noop',
           'tgettext', 'tgettext_noop', 'tngettext', 'tngettext_noop']

def safefmt(string, kwargs):
    if kwargs:
        try:
            return string % kwargs
        except KeyError:
            pass
    return string

def gettext_noop(string, **kwargs):
    return safefmt(string, kwargs)

def dgettext_noop(domain, string, **kwargs):
    return gettext_noop(string, **kwargs)

N_ = gettext_noop

def ngettext_noop(singular, plural, num, **kwargs):
    string = (plural, singular)[num == 1]
    kwargs.setdefault('num', num)
    return safefmt(string, kwargs)

def dngettext_noop(domain, singular, plural, num, **kwargs):
    return ngettext_noop(singular, plural, num, **kwargs)

_param_re = re.compile(r"%\((\w+)\)(?:s|[\d]*d|\d*.?\d*[fg])")
def _tag_kwargs(trans, kwargs):
    trans_elts = _param_re.split(trans)
    for i in xrange(1, len(trans_elts), 2):
        trans_elts[i] = kwargs.get(trans_elts[i], '???')
    return tag(*trans_elts)

def tgettext_noop(string, **kwargs):
    return kwargs and _tag_kwargs(string, kwargs) or string

def dtgettext_noop(domain, string, **kwargs):
    return tgettext_noop(string, **kwargs)

def tngettext_noop(singular, plural, num, **kwargs):
    string = (plural, singular)[num == 1]
    kwargs.setdefault('num', num)
    return _tag_kwargs(string, kwargs)

def dtngettext_noop(domain, singular, plural, num, **kwargs):
    return tngettext_noop(singular, plural, num, **kwargs)

def add_domain(domain, env_path, locale_dir):
    pass

def domain_functions(domain, *symbols):
    _functions = {
      'gettext': gettext_noop,
      '_': gettext_noop,
      'N_': gettext_noop,
      'ngettext': ngettext_noop,
      'tgettext': tgettext_noop,
      'tag_': tgettext_noop,
      'tngettext': tngettext_noop,
      'add_domain': lambda env_path, locale_dir: None,
      }
    return [_functions[s] for s in symbols]

try:
    from babel.support import LazyProxy, Translations
    from gettext import NullTranslations

    class TranslationsProxy(object):
        """Delegate Translations calls to the currently active Translations.

        If there's none, wrap those calls in LazyProxy objects.
        """

        def __init__(self):
            self._current = threading.local()
            self._null_translations = NullTranslations()
            self._plugin_domains = {}
            self._plugin_domains_lock = threading.RLock()

        # Public API

        def add_domain(self, domain, env_path, locales_dir):
            self._plugin_domains_lock.acquire()
            try:
                if env_path not in self._plugin_domains:
                    self._plugin_domains[env_path] = []
                self._plugin_domains[env_path].append((domain, locales_dir))
            finally:
                self._plugin_domains_lock.release()

        def activate(self, locale, env_path=None):
            locale_dir = pkg_resources.resource_filename('trac', 'locale')
            t = Translations.load(locale_dir, locale)# or 'en_US')
            if env_path:
                self._plugin_domains_lock.acquire()
                try:
                    domains = list(self._plugin_domains.get(env_path, []))
                finally:
                    self._plugin_domains_lock.release()
                for domain, dirname in domains:
                    t.add(Translations.load(dirname, locale, domain))
            self._current.translations = t

        def deactivate(self):
            del self._current.translations

        @property
        def active(self):
            return getattr(self._current, 'translations',
                           self._null_translations)

        @property
        def isactive(self):
            return hasattr(self._current, 'translations')

        # Delegated methods

        def __getattr__(self, name):
            return getattr(self.active, name)

        def gettext(self, string, **kwargs):
            def _gettext():
                return safefmt(self.active.ugettext(string), kwargs)
            if not self.isactive:
                return LazyProxy(_gettext)
            return _gettext()

        def dgettext(self, domain, string, **kwargs):
            def _dgettext():
                return safefmt(self.active.dugettext(domain, string), kwargs)
            if not self.isactive:
                return LazyProxy(_dgettext)
            return _dgettext()

        def ngettext(self, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            kwargs.setdefault('num', num)
            def _ngettext():
                trans = self.active.ungettext(singular, plural, num)
                return safefmt(trans, kwargs)
            if not self.isactive:
                return LazyProxy(_ngettext)
            return _ngettext()

        def dngettext(self, domain, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            kwargs.setdefault('num', num)
            def _dngettext():
                trans = self.active.dungettext(domain, singular, plural, num)
                return safefmt(trans, kwargs)
            if not self.isactive:
                return LazyProxy(_dngettext)
            return _dngettext()

        def tgettext(self, string, **kwargs):
            def _tgettext():
                trans = self.active.ugettext(string)
                return kwargs and _tag_kwargs(trans, kwargs) or trans
            if not self.isactive:
                return LazyProxy(_tgettext)
            return _tgettext()

        def dtgettext(self, domain, string, **kwargs):
            def _dtgettext():
                trans = self.active.dugettext(domain, string)
                return kwargs and _tag_kwargs(trans, kwargs) or trans
            if not self.isactive:
                return LazyProxy(_dtgettext)
            return _dtgettext()

        def tngettext(self, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            kwargs.setdefault('num', num)
            def _tngettext():
                trans = self.active.ungettext(singular, plural, num)
                return _tag_kwargs(trans, kwargs)
            if not self.isactive:
                return LazyProxy(_tngettext)
            return _tngettext()

        def dtngettext(self, domain, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            def _dtngettext():
                trans = self.active.dungettext(domain, singular, plural, num)
                if '%(num)' in trans:
                    kwargs.update(num=num)
                return kwargs and _tag_kwargs(trans, kwargs) or trans
            if not self.isactive:
                return LazyProxy(_dtngettext)
            return _dtngettext()

    translations = TranslationsProxy()

    def domain_functions(domain, *symbols):
        _functions = {
          'gettext': translations.dgettext,
          '_': translations.dgettext,
          'ngettext': translations.dngettext,
          'tgettext': translations.dtgettext,
          'tag_': translations.dtgettext,
          'tngettext': translations.dtngettext,
          'add_domain': translations.add_domain,
          }
        def wrapdomain(symbol):
            if symbol == 'N_':
                return gettext_noop
            return lambda *args, **kw: _functions[symbol](domain, *args, **kw)
        return [wrapdomain(s) for s in symbols]

    gettext = translations.gettext
    _ = gettext
    dgettext = translations.dgettext
    ngettext = translations.ngettext
    dngettext = translations.dngettext
    tgettext = translations.tgettext
    tag_ = tgettext
    dtgettext = translations.dtgettext
    tngettext = translations.tngettext
    dtngettext = translations.dtngettext

    def deactivate():
        translations.deactivate()

    def activate(locale, env_path=None):
        translations.activate(locale, env_path)

    def add_domain(domain, env_path, locale_dir):
        translations.add_domain(domain, env_path, locale_dir)

    def get_translations():
        return translations

    def get_available_locales():
        """Return a list of locale identifiers of the locales for which
        translations are available.
        """
        return [dirname for dirname
                in pkg_resources.resource_listdir('trac', 'locale')
                if '.' not in dirname]

except ImportError: # fall back on 0.11 behavior, i18n functions are no-ops
    gettext = _ = gettext_noop
    dgettext = dgettext_noop
    ngettext = ngettext_noop
    dngettext = dngettext_noop
    tgettext = tag_ = tgettext_noop
    dtgettext = dtgettext_noop
    tngettext = tngettext_noop
    dtngettext = dtngettext_noop

    def activate(locale, env_path=None):
        pass

    def deactivate():
        pass

    def get_translations():
        # for correctness we should return a NullTranslations subclass,
        # like the DummyTranslations one can find in genshi/filters/i18n.py,
        # but this works just as well for our current needs:
        return gettext_noop

    def get_available_locales():
        return []
<commit_msg>i18n: make !TranslationsProxy activation more robust.<commit_after># -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Edgewall Software
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.

"""Utilities for text translation with gettext."""

import re
import sys
try:
    import threading
except ImportError:
    import dummy_threading as threading

import pkg_resources

from genshi.builder import tag

__all__ = ['gettext', 'ngettext', 'gettext_noop', 'ngettext_noop',
           'tgettext', 'tgettext_noop', 'tngettext', 'tngettext_noop']

def safefmt(string, kwargs):
    if kwargs:
        try:
            return string % kwargs
        except KeyError:
            pass
    return string

def gettext_noop(string, **kwargs):
    return safefmt(string, kwargs)

def dgettext_noop(domain, string, **kwargs):
    return gettext_noop(string, **kwargs)

N_ = gettext_noop

def ngettext_noop(singular, plural, num, **kwargs):
    string = (plural, singular)[num == 1]
    kwargs.setdefault('num', num)
    return safefmt(string, kwargs)

def dngettext_noop(domain, singular, plural, num, **kwargs):
    return ngettext_noop(singular, plural, num, **kwargs)

_param_re = re.compile(r"%\((\w+)\)(?:s|[\d]*d|\d*.?\d*[fg])")
def _tag_kwargs(trans, kwargs):
    trans_elts = _param_re.split(trans)
    for i in xrange(1, len(trans_elts), 2):
        trans_elts[i] = kwargs.get(trans_elts[i], '???')
    return tag(*trans_elts)

def tgettext_noop(string, **kwargs):
    return kwargs and _tag_kwargs(string, kwargs) or string

def dtgettext_noop(domain, string, **kwargs):
    return tgettext_noop(string, **kwargs)

def tngettext_noop(singular, plural, num, **kwargs):
    string = (plural, singular)[num == 1]
    kwargs.setdefault('num', num)
    return _tag_kwargs(string, kwargs)

def dtngettext_noop(domain, singular, plural, num, **kwargs):
    return tngettext_noop(singular, plural, num, **kwargs)

def add_domain(domain, env_path, locale_dir):
    pass

def domain_functions(domain, *symbols):
    _functions = {
      'gettext': gettext_noop,
      '_': gettext_noop,
      'N_': gettext_noop,
      'ngettext': ngettext_noop,
      'tgettext': tgettext_noop,
      'tag_': tgettext_noop,
      'tngettext': tngettext_noop,
      'add_domain': lambda env_path, locale_dir: None,
      }
    return [_functions[s] for s in symbols]

try:
    from babel.support import LazyProxy, Translations
    from gettext import NullTranslations

    class TranslationsProxy(object):
        """Delegate Translations calls to the currently active Translations.

        If there's none, wrap those calls in LazyProxy objects.
        """

        def __init__(self):
            self._current = threading.local()
            self._null_translations = NullTranslations()
            self._plugin_domains = {}
            self._plugin_domains_lock = threading.RLock()

        # Public API

        def add_domain(self, domain, env_path, locales_dir):
            self._plugin_domains_lock.acquire()
            try:
                if env_path not in self._plugin_domains:
                    self._plugin_domains[env_path] = []
                self._plugin_domains[env_path].append((domain, locales_dir))
            finally:
                self._plugin_domains_lock.release()

        def activate(self, locale, env_path=None):
            locale_dir = pkg_resources.resource_filename('trac', 'locale')
            t = Translations.load(locale_dir, locale) # or 'en_US')
            if not t or t.__class__ is NullTranslations:
                return
            if env_path:
                self._plugin_domains_lock.acquire()
                try:
                    domains = list(self._plugin_domains.get(env_path, []))
                finally:
                    self._plugin_domains_lock.release()
                for domain, dirname in domains:
                    t.add(Translations.load(dirname, locale, domain))
            self._current.translations = t

        def deactivate(self):
            if self.isactive:
                del self._current.translations

        @property
        def active(self):
            return getattr(self._current, 'translations',
                           self._null_translations)

        @property
        def isactive(self):
            return hasattr(self._current, 'translations')

        # Delegated methods

        def __getattr__(self, name):
            return getattr(self.active, name)

        def gettext(self, string, **kwargs):
            def _gettext():
                return safefmt(self.active.ugettext(string), kwargs)
            if not self.isactive:
                return LazyProxy(_gettext)
            return _gettext()

        def dgettext(self, domain, string, **kwargs):
            def _dgettext():
                return safefmt(self.active.dugettext(domain, string), kwargs)
            if not self.isactive:
                return LazyProxy(_dgettext)
            return _dgettext()

        def ngettext(self, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            kwargs.setdefault('num', num)
            def _ngettext():
                trans = self.active.ungettext(singular, plural, num)
                return safefmt(trans, kwargs)
            if not self.isactive:
                return LazyProxy(_ngettext)
            return _ngettext()

        def dngettext(self, domain, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            kwargs.setdefault('num', num)
            def _dngettext():
                trans = self.active.dungettext(domain, singular, plural, num)
                return safefmt(trans, kwargs)
            if not self.isactive:
                return LazyProxy(_dngettext)
            return _dngettext()

        def tgettext(self, string, **kwargs):
            def _tgettext():
                trans = self.active.ugettext(string)
                return kwargs and _tag_kwargs(trans, kwargs) or trans
            if not self.isactive:
                return LazyProxy(_tgettext)
            return _tgettext()

        def dtgettext(self, domain, string, **kwargs):
            def _dtgettext():
                trans = self.active.dugettext(domain, string)
                return kwargs and _tag_kwargs(trans, kwargs) or trans
            if not self.isactive:
                return LazyProxy(_dtgettext)
            return _dtgettext()

        def tngettext(self, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            kwargs.setdefault('num', num)
            def _tngettext():
                trans = self.active.ungettext(singular, plural, num)
                return _tag_kwargs(trans, kwargs)
            if not self.isactive:
                return LazyProxy(_tngettext)
            return _tngettext()

        def dtngettext(self, domain, singular, plural, num, **kwargs):
            kwargs = kwargs.copy()
            def _dtngettext():
                trans = self.active.dungettext(domain, singular, plural, num)
                if '%(num)' in trans:
                    kwargs.update(num=num)
                return kwargs and _tag_kwargs(trans, kwargs) or trans
            if not self.isactive:
                return LazyProxy(_dtngettext)
            return _dtngettext()

    translations = TranslationsProxy()

    def domain_functions(domain, *symbols):
        _functions = {
          'gettext': translations.dgettext,
          '_': translations.dgettext,
          'ngettext': translations.dngettext,
          'tgettext': translations.dtgettext,
          'tag_': translations.dtgettext,
          'tngettext': translations.dtngettext,
          'add_domain': translations.add_domain,
          }
        def wrapdomain(symbol):
            if symbol == 'N_':
                return gettext_noop
            return lambda *args, **kw: _functions[symbol](domain, *args, **kw)
        return [wrapdomain(s) for s in symbols]

    gettext = translations.gettext
    _ = gettext
    dgettext = translations.dgettext
    ngettext = translations.ngettext
    dngettext = translations.dngettext
    tgettext = translations.tgettext
    tag_ = tgettext
    dtgettext = translations.dtgettext
    tngettext = translations.tngettext
    dtngettext = translations.dtngettext

    def deactivate():
        translations.deactivate()

    def activate(locale, env_path=None):
        translations.activate(locale, env_path)

    def add_domain(domain, env_path, locale_dir):
        translations.add_domain(domain, env_path, locale_dir)

    def get_translations():
        return translations

    def get_available_locales():
        """Return a list of locale identifiers of the locales for which
        translations are available.
        """
        return [dirname for dirname
                in pkg_resources.resource_listdir('trac', 'locale')
                if '.' not in dirname]

except ImportError: # fall back on 0.11 behavior, i18n functions are no-ops
    gettext = _ = gettext_noop
    dgettext = dgettext_noop
    ngettext = ngettext_noop
    dngettext = dngettext_noop
    tgettext = tag_ = tgettext_noop
    dtgettext = dtgettext_noop
    tngettext = tngettext_noop
    dtngettext = dtngettext_noop

    def activate(locale, env_path=None):
        pass

    def deactivate():
        pass

    def get_translations():
        # for correctness we should return a NullTranslations subclass,
        # like the DummyTranslations one can find in genshi/filters/i18n.py,
        # but this works just as well for our current needs:
        return gettext_noop

    def get_available_locales():
        return []
<|endoftext|>


<commit_before>import unittest

from trac.core import *
from trac.test import EnvironmentStub
from trac.wiki import WikiPage, IWikiChangeListener

class TestWikiChangeListener(Component):
    implements(IWikiChangeListener)
    def __init__(self):
        self.added = []
        self.changed = []
        self.deleted = []

    def wiki_page_added(self, page):
        self.added.append(page)

    def wiki_page_changed(self, page, version, t, comment, author, ipnr):
        self.changed.append((page, version, t, comment, author, ipnr))

    def wiki_page_deleted(self, page):
        self.deleted.append(page)

class WikiPageTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub()
        self.db = self.env.get_db_cnx()

    def test_new_page(self):
        page = WikiPage(self.env)
        self.assertEqual(False, page.exists)
        self.assertEqual(None, page.name)
        self.assertEqual(0, page.version)
        self.assertEqual('', page.text)
        self.assertEqual(0, page.readonly)

    def test_existing_page(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 0, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        self.assertEqual('TestPage', page.name)
        self.assertEqual(0, page.version)
        self.assertEqual('Bla bla', page.text)
        self.assertEqual(False, page.readonly)
        history = list(page.get_history())
        self.assertEqual(1, len(history))
        self.assertEqual((0, 42, 'joe', 'Testing', '::1'), history[0])

    def test_create_page(self):
        page = WikiPage(self.env)
        page.name = 'TestPage'
        page.text = 'Bla bla'
        page.save('joe', 'Testing', '::1', 42)

        cursor = self.db.cursor()
        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual((1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0),
                         cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual(page, listener.added[0])

    def test_update_page(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 1, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        page.text = 'Bla'
        page.save('kate', 'Changing', '192.168.0.101', 43)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual((1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0),
                         cursor.fetchone())
        self.assertEqual((2, 43, 'kate', '192.168.0.101', 'Bla',
                          'Changing', 0), cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual((page, 2, 43, 'Changing', 'kate', '192.168.0.101'),
                         listener.changed[0])

        page = WikiPage(self.env, 'TestPage')
        history = list(page.get_history())
        self.assertEqual(2, len(history))
        self.assertEqual((2, 43, 'kate', 'Changing', '192.168.0.101'),
                         history[0])
        self.assertEqual((1, 42, 'joe', 'Testing', '::1'), history[1])

    def test_delete_page(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 1, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        page.delete()

        self.assertEqual(False, page.exists)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual(None, cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual(page, listener.deleted[0])

    def test_delete_page_version(self):
        cursor = self.db.cursor()
        cursor.executemany("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                           [('TestPage', 1, 42, 'joe', '::1', 'Bla bla',
                            'Testing', 0),
                            ('TestPage', 2, 43, 'kate', '192.168.0.101', 'Bla',
                            'Changing', 0)])

        page = WikiPage(self.env, 'TestPage')
        page.delete(version=2)

        self.assertEqual(True, page.exists)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual((1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0),
                         cursor.fetchone())
        self.assertEqual(None, cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual(0, len(listener.deleted))

    def test_delete_page_last_version(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 1, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        page.delete(version=1)

        self.assertEqual(False, page.exists)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual(None, cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual(page, listener.deleted[0])

def suite():
    return unittest.makeSuite(WikiPageTestCase, 'test')

if __name__ == '__main__':
    unittest.main(defaultTest='suite')
<commit_msg>Updated Wiki unit tests for change in r3206.<commit_after>import unittest

from trac.core import *
from trac.test import EnvironmentStub
from trac.wiki import WikiPage, IWikiChangeListener

class TestWikiChangeListener(Component):
    implements(IWikiChangeListener)
    def __init__(self):
        self.added = []
        self.changed = []
        self.deleted = []
        self.deleted_version = []

    def wiki_page_added(self, page):
        self.added.append(page)

    def wiki_page_changed(self, page, version, t, comment, author, ipnr):
        self.changed.append((page, version, t, comment, author, ipnr))

    def wiki_page_deleted(self, page):
        self.deleted.append(page)

    def wiki_page_version_deleted(self, page):
        self.deleted_version.append(page)

class WikiPageTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub()
        self.db = self.env.get_db_cnx()

    def test_new_page(self):
        page = WikiPage(self.env)
        self.assertEqual(False, page.exists)
        self.assertEqual(None, page.name)
        self.assertEqual(0, page.version)
        self.assertEqual('', page.text)
        self.assertEqual(0, page.readonly)

    def test_existing_page(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 0, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        self.assertEqual('TestPage', page.name)
        self.assertEqual(0, page.version)
        self.assertEqual('Bla bla', page.text)
        self.assertEqual(False, page.readonly)
        history = list(page.get_history())
        self.assertEqual(1, len(history))
        self.assertEqual((0, 42, 'joe', 'Testing', '::1'), history[0])

    def test_create_page(self):
        page = WikiPage(self.env)
        page.name = 'TestPage'
        page.text = 'Bla bla'
        page.save('joe', 'Testing', '::1', 42)

        cursor = self.db.cursor()
        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual((1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0),
                         cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual(page, listener.added[0])

    def test_update_page(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 1, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        page.text = 'Bla'
        page.save('kate', 'Changing', '192.168.0.101', 43)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual((1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0),
                         cursor.fetchone())
        self.assertEqual((2, 43, 'kate', '192.168.0.101', 'Bla',
                          'Changing', 0), cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual((page, 2, 43, 'Changing', 'kate', '192.168.0.101'),
                         listener.changed[0])

        page = WikiPage(self.env, 'TestPage')
        history = list(page.get_history())
        self.assertEqual(2, len(history))
        self.assertEqual((2, 43, 'kate', 'Changing', '192.168.0.101'),
                         history[0])
        self.assertEqual((1, 42, 'joe', 'Testing', '::1'), history[1])

    def test_delete_page(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 1, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        page.delete()

        self.assertEqual(False, page.exists)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual(None, cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual(page, listener.deleted[0])

    def test_delete_page_version(self):
        cursor = self.db.cursor()
        cursor.executemany("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                           [('TestPage', 1, 42, 'joe', '::1', 'Bla bla',
                            'Testing', 0),
                            ('TestPage', 2, 43, 'kate', '192.168.0.101', 'Bla',
                            'Changing', 0)])

        page = WikiPage(self.env, 'TestPage')
        page.delete(version=2)

        self.assertEqual(True, page.exists)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual((1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0),
                         cursor.fetchone())
        self.assertEqual(None, cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertTrue(page, listener.deleted_version[0])

    def test_delete_page_last_version(self):
        cursor = self.db.cursor()
        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
                       ('TestPage', 1, 42, 'joe', '::1', 'Bla bla', 'Testing',
                        0))

        page = WikiPage(self.env, 'TestPage')
        page.delete(version=1)

        self.assertEqual(False, page.exists)

        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
        self.assertEqual(None, cursor.fetchone())

        listener = TestWikiChangeListener(self.env)
        self.assertEqual(page, listener.deleted[0])

def suite():
    return unittest.makeSuite(WikiPageTestCase, 'test')

if __name__ == '__main__':
    unittest.main(defaultTest='suite')
<|endoftext|>