<commit_before># -*- coding: utf-8 -*-
"""
LICENCE
-------
Copyright 2015 by Kitware, Inc. All Rights Reserved. Please refer to
KITWARE_LICENSE.TXT for licensing information, or contact General Counsel,
Kitware, Inc., 28 Corporate Drive, Clifton Park, NY 12065.

"""

import base64
import flask
import logging
import mimetypes
import multiprocessing
import os
import requests
import tempfile

from smqtk.data_rep.data_element_impl.file_element import FileElement
from smqtk.data_rep.data_element_impl.memory_element import MemoryElement
from smqtk.data_rep.data_element_impl.url_element import UrlElement
from smqtk.utils import SimpleTimer
from smqtk.utils.configuration import ContentDescriptorConfiguration

MIMETYPES = mimetypes.MimeTypes()

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

class DescriptorServiceServer (flask.Flask):
    """
    Simple server that takes in a specification of the following form:

        /<descriptor_type>/<uri>[?...]

    Computes the requested descriptor for the given file and returns that via
    a JSON structure.

    Standard return JSON:
    {
        "success": <bool>,
        "descriptor": [ <float>, ... ]
        "message": <string>,
        "reference_uri": <uri>
    }

    """

    # Optional environment variable that can point to a configuration file
    ENV_CONFIG = "DescriptorService_CONFIG"

    @property
    def log(self):
        return logging.getLogger('.'.join((self.__module__,
                                           self.__class__.__name__)))

    def __init__(self, config_filepath=None):
        super(DescriptorServiceServer, self).__init__(
            self.__class__.__name__,
            static_folder=os.path.join(SCRIPT_DIR, 'static'),
            template_folder=os.path.join(SCRIPT_DIR, 'templates')
        )

        #
        # Configuration setup
        #
        config_env_loaded = config_file_loaded = None

        # Load default -- This should always be present, aka base defaults
        self.config.from_object('smqtk_config')
        config_default_loaded = True

        # Load from env var if present
        if self.ENV_CONFIG in os.environ:
            self.log.info("Loading config from env var (%s)...",
                          self.ENV_CONFIG)
            self.config.from_envvar(self.ENV_CONFIG)
            config_env_loaded = True

        # Load from configuration file if given
        if config_filepath and os.path.isfile(config_filepath):
            config_file_path = \
                os.path.expanduser(os.path.abspath(config_filepath))
            self.log.info("Loading config from file (%s)...", config_file_path)
            self.config.from_pyfile(config_file_path)
            config_file_loaded = True

        self.log.debug("Config defaults loaded : %s", config_default_loaded)
        self.log.debug("Config from env loaded : %s", config_env_loaded)
        self.log.debug("Config from file loaded: %s", config_file_loaded)
        if not (config_default_loaded or config_env_loaded
                or config_file_loaded):
            raise RuntimeError("No configuration file specified for loading. "
                               "(%s=%s) (file=%s)"
                               % (self.ENV_CONFIG,
                                  os.environ.get(self.ENV_CONFIG, None),
                                  config_filepath))

        # Cache of ContentDescriptor instances
        descriptor_cache = {}
        descriptor_cache_lock = multiprocessing.RLock()

        #
        # Security
        #
        self.secret_key = self.config['SECRET_KEY']

        @self.route("/")
        def list_ingest_labels():
            return flask.jsonify({
                "labels": sorted(ContentDescriptorConfiguration
                                 .available_labels())
            })

        @self.route("/<string:descriptor_label>/<path:uri>")
        def compute_descriptor(descriptor_label, uri):
            """
            # Data modes for upload/use
                - local filepath
                - base64
                - http/s URL

            The following sub-sections detail how different URI's can be used.

            ## Local Filepath
            The URI string must be prefixed with ``file://``, followed by the
            full path to the data file to describe.

            ## Base 64 data
            The URI string must be prefixed with "base64://", followed by the
            base64 encoded string. This mode also requires an additional
            ``?content_type=`` to provide data content type information. This
            mode saves the encoded data to temporary file for processing.

            ## HTTP/S address
            This is the default mode when the URI prefix is none of the above.
            This uses the requests module to locally download a data file
            for processing.

            """
            success = True
            message = "Nothing has happened yet"

            # Data element to compute a descriptor for
            de = None

            # Resolve URI into DataElement instance
            if uri[:7] == "file://":
                self.log.debug("Given local disk filepath")
                filepath = uri[7:]
                if not os.path.isfile(filepath):
                    success = False
                    message = "File URI did not point to an existing file on " \
                              "disk."
                else:
                    de = FileElement(filepath)

            elif uri[:9] == "base64://":
                self.log.debug("Given base64 string")
                content_type = flask.request.args.get('content_type', None)
                self.log.debug("Content type: %s", content_type)
                if not content_type:
                    self.log.warning("No content-type with given base64 data")
                    success = False
                    message = "No content-type with given base64 content."
                else:
                    b64str = uri[9:]
                    de = MemoryElement.from_base64(b64str, content_type)

            else:
                self.log.debug("Given URL")
                de = UrlElement(uri)

            descriptor = None
            if success:  # so far...
                # Get the descriptor instance for the given label, creating it
                # if necessary.
                if descriptor_label not in descriptor_cache:
                    self.log.debug("Creating descriptor '%s'", descriptor_label)
                    with descriptor_cache_lock:
                        descriptor_cache[descriptor_label] = \
                            ContentDescriptorConfiguration\
                            .new_inst(descriptor_label)
                with descriptor_cache_lock:
                    #: :type: smqtk.content_description.ContentDescriptor
                    d = descriptor_cache[descriptor_label]
                with SimpleTimer("Computing descriptor...", self.log.debug):
                    descriptor = d.compute_descriptor(de)
                    message = "Descriptor computed of type '%s'" \
                              % descriptor_label

            return flask.jsonify({
                "success": success,
                "message": message,
                "descriptor": descriptor.tolist(),
                "reference_uri": uri
            })

    def run(self, host=None, port=None, debug=False, **options):
        """
        Override of the run method, drawing running host and port from
        configuration by default. 'host' and 'port' values specified as argument
        or keyword will override the app configuration.
        """
        super(DescriptorServiceServer, self)\
            .run(host=(host or self.config['RUN_HOST']),
                 port=(port or self.config['RUN_PORT']),
                 **options)
<commit_msg>Updated descriptor server element usage<commit_after># -*- coding: utf-8 -*-
"""
LICENCE
-------
Copyright 2015 by Kitware, Inc. All Rights Reserved. Please refer to
KITWARE_LICENSE.TXT for licensing information, or contact General Counsel,
Kitware, Inc., 28 Corporate Drive, Clifton Park, NY 12065.

"""

import base64
import flask
import logging
import mimetypes
import multiprocessing
import os
import requests
import tempfile

from smqtk.data_rep.data_element_impl.file_element import DataFileElement
from smqtk.data_rep.data_element_impl.memory_element import DataMemoryElement
from smqtk.data_rep.data_element_impl.url_element import DataUrlElement
from smqtk.utils import SimpleTimer
from smqtk.utils.configuration import ContentDescriptorConfiguration

MIMETYPES = mimetypes.MimeTypes()

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

class DescriptorServiceServer (flask.Flask):
    """
    Simple server that takes in a specification of the following form:

        /<descriptor_type>/<uri>[?...]

    Computes the requested descriptor for the given file and returns that via
    a JSON structure.

    Standard return JSON:
    {
        "success": <bool>,
        "descriptor": [ <float>, ... ]
        "message": <string>,
        "reference_uri": <uri>
    }

    """

    # Optional environment variable that can point to a configuration file
    ENV_CONFIG = "DescriptorService_CONFIG"

    @property
    def log(self):
        return logging.getLogger('.'.join((self.__module__,
                                           self.__class__.__name__)))

    def __init__(self, config_filepath=None):
        super(DescriptorServiceServer, self).__init__(
            self.__class__.__name__,
            static_folder=os.path.join(SCRIPT_DIR, 'static'),
            template_folder=os.path.join(SCRIPT_DIR, 'templates')
        )

        #
        # Configuration setup
        #
        config_env_loaded = config_file_loaded = None

        # Load default -- This should always be present, aka base defaults
        self.config.from_object('smqtk_config')
        config_default_loaded = True

        # Load from env var if present
        if self.ENV_CONFIG in os.environ:
            self.log.info("Loading config from env var (%s)...",
                          self.ENV_CONFIG)
            self.config.from_envvar(self.ENV_CONFIG)
            config_env_loaded = True

        # Load from configuration file if given
        if config_filepath and os.path.isfile(config_filepath):
            config_file_path = \
                os.path.expanduser(os.path.abspath(config_filepath))
            self.log.info("Loading config from file (%s)...", config_file_path)
            self.config.from_pyfile(config_file_path)
            config_file_loaded = True

        self.log.debug("Config defaults loaded : %s", config_default_loaded)
        self.log.debug("Config from env loaded : %s", config_env_loaded)
        self.log.debug("Config from file loaded: %s", config_file_loaded)
        if not (config_default_loaded or config_env_loaded
                or config_file_loaded):
            raise RuntimeError("No configuration file specified for loading. "
                               "(%s=%s) (file=%s)"
                               % (self.ENV_CONFIG,
                                  os.environ.get(self.ENV_CONFIG, None),
                                  config_filepath))

        # Cache of ContentDescriptor instances
        descriptor_cache = {}
        descriptor_cache_lock = multiprocessing.RLock()

        #
        # Security
        #
        self.secret_key = self.config['SECRET_KEY']

        @self.route("/")
        def list_ingest_labels():
            return flask.jsonify({
                "labels": sorted(ContentDescriptorConfiguration
                                 .available_labels())
            })

        @self.route("/<string:descriptor_label>/<path:uri>")
        def compute_descriptor(descriptor_label, uri):
            """
            # Data modes for upload/use
                - local filepath
                - base64
                - http/s URL

            The following sub-sections detail how different URI's can be used.

            ## Local Filepath
            The URI string must be prefixed with ``file://``, followed by the
            full path to the data file to describe.

            ## Base 64 data
            The URI string must be prefixed with "base64://", followed by the
            base64 encoded string. This mode also requires an additional
            ``?content_type=`` to provide data content type information. This
            mode saves the encoded data to temporary file for processing.

            ## HTTP/S address
            This is the default mode when the URI prefix is none of the above.
            This uses the requests module to locally download a data file
            for processing.

            """
            success = True
            message = "Nothing has happened yet"

            # Data element to compute a descriptor for
            de = None

            # Resolve URI into DataElement instance
            if uri[:7] == "file://":
                self.log.debug("Given local disk filepath")
                filepath = uri[7:]
                if not os.path.isfile(filepath):
                    success = False
                    message = "File URI did not point to an existing file on " \
                              "disk."
                else:
                    de = DataFileElement(filepath)

            elif uri[:9] == "base64://":
                self.log.debug("Given base64 string")
                content_type = flask.request.args.get('content_type', None)
                self.log.debug("Content type: %s", content_type)
                if not content_type:
                    self.log.warning("No content-type with given base64 data")
                    success = False
                    message = "No content-type with given base64 content."
                else:
                    b64str = uri[9:]
                    de = DataMemoryElement.from_base64(b64str, content_type)

            else:
                self.log.debug("Given URL")
                de = DataUrlElement(uri)

            descriptor = None
            if success:  # so far...
                # Get the descriptor instance for the given label, creating it
                # if necessary.
                if descriptor_label not in descriptor_cache:
                    self.log.debug("Creating descriptor '%s'", descriptor_label)
                    with descriptor_cache_lock:
                        descriptor_cache[descriptor_label] = \
                            ContentDescriptorConfiguration\
                            .new_inst(descriptor_label)
                with descriptor_cache_lock:
                    #: :type: smqtk.content_description.ContentDescriptor
                    d = descriptor_cache[descriptor_label]
                with SimpleTimer("Computing descriptor...", self.log.debug):
                    descriptor = d.compute_descriptor(de)
                    message = "Descriptor computed of type '%s'" \
                              % descriptor_label

            return flask.jsonify({
                "success": success,
                "message": message,
                "descriptor": descriptor.tolist(),
                "reference_uri": uri
            })

    def run(self, host=None, port=None, debug=False, **options):
        """
        Override of the run method, drawing running host and port from
        configuration by default. 'host' and 'port' values specified as argument
        or keyword will override the app configuration.
        """
        super(DescriptorServiceServer, self)\
            .run(host=(host or self.config['RUN_HOST']),
                 port=(port or self.config['RUN_PORT']),
                 **options)
<|endoftext|>


<commit_before>import itertools
import os

from twisted.trial import unittest, itrial
from twisted.internet import defer, utils
from ooni.utils import log

pyunit = __import__('unittest')

class InputTestSuite(pyunit.TestSuite):
    """
    This in an extension of a unittest test suite. It adds support for inputs
    and the tracking of current index via idx.
    """
    def run(self, result, idx=0):
        self._idx = idx
        while self._tests:
            if result.shouldStop:
                break
            test = self._tests.pop(0)
            try:
                test.input = self.input
                test._idx = self._idx
                test(result)
            except:
                test(result)
            self._idx += 1
        return result

class TestCase(unittest.TestCase):
    """
    This is the monad of the OONI nettest universe. When you write a nettest
    you will subclass this object.

    * inputs: can be set to a static set of inputs. All the tests (the methods
      starting with the "test_" prefix) will be run once per input.  At every run
      the _input_ attribute of the TestCase instance will be set to the value of
      the current iteration over inputs.  Any python iterable object can be set
      to inputs.

    * inputFile: attribute should be set to an array containing the command line
      argument that should be used as the input file. Such array looks like
      this:

          ``["commandlinearg", "c", "The description"]``

      The second value of such arrray is the shorthand for the command line arg.
      The user will then be able to specify inputs to the test via:

          ``ooniprobe mytest.py --commandlinearg path/to/file.txt``

      or

          ``ooniprobe mytest.py -c path/to/file.txt``

    * inputProcessor: should be set to a function that takes as argument an
      open file descriptor and it will yield the input to be passed to the test
      instance.

    * name: should be set to the name of the test.

    * author: should contain the name and contact details for the test author.
      The format for such string is as follows:

          ``The Name <email@example.com>``

    * version: is the version string of the test.
    """
    name = "I Did Not Change The Name"
    author = "John Doe <foo@example.com>"
    version = "0"

    inputs = [None]
    inputFile = None

    report = {}
    report['errors'] = []

    optParameters = None

    def _raaun(self, methodName, result):
        from twisted.internet import reactor
        method = getattr(self, methodName)
        log.debug("Running %s" % methodName)
        d = defer.maybeDeferred(
                utils.runWithWarningsSuppressed, self._getSuppress(), method)
        d.addBoth(lambda x : call.active() and call.cancel() or x)
        return d

    def deferSetUp(self, ignored, result):
        """
        If we have the reporterFactory set we need to write the header. If such
        method is not present we will only run the test skipping header
        writing.
        """
        if result.reporterFactory.firstrun:
            d1 = result.reporterFactory.writeHeader()
            d2 = unittest.TestCase.deferSetUp(self, ignored, result)
            dl = defer.DeferredList([d1, d2])
            return dl
        else:
            return unittest.TestCase.deferSetUp(self, ignored, result)

    def inputProcessor(self, fp):
        for x in fp.readlines():
            yield x.strip()
        fp.close()

    def getOptions(self):
        if self.inputFile:
            try:
                assert isinstance(self.inputFile, str)
            except AssertionError, ae:
                log.err(ae)
            else:
                if os.path.isfile(self.inputFile):
                    print self.inputFile
                    fp = open(self.inputFile)
                    self.inputs = self.inputProcessor(fp)
        elif not self.inputs[0]:
            pass
        elif self.inputFile:
            raise usage.UsageError("No input file specified!")
        # XXX perhaps we may want to name and version to be inside of a
        # different object that is not called options.
        return {'inputs': self.inputs,
                'name': self.name,
                'version': self.version}

    def __repr__(self):
        return "<%s inputs=%s>" % (self.__class__, self.inputs)

<commit_msg>* Refactored nettest.TestCase to get it to recognize test suboptions.<commit_after># -*- coding: utf-8 -*-

import itertools
import os

from inspect                   import classify_class_attrs
from pprint                    import pprint

from twisted.internet          import defer, utils
from twisted.python            import usage
from twisted.trial             import unittest, itrial
from zope.interface.exceptions import BrokenImplementation

from ooni.inputunit            import InputUnitProcessor
from ooni.utils                import log
from ooni.utils.assertions     import isClass, isNotClass
from ooni.utils.assertions     import isOldStyleClass, isNewStyleClass

pyunit = __import__('unittest')

class InputTestSuite(pyunit.TestSuite):
    """
    This in an extension of a unittest test suite. It adds support for inputs
    and the tracking of current index via idx.
    """
    def run(self, result, idx=0):
        self._idx = idx
        while self._tests:
            if result.shouldStop:
                break
            test = self._tests.pop(0)
            try:
                test.input = self.input
                test._idx = self._idx
                test(result)
            except:
                test(result)
            self._idx += 1
        return result

class TestCase(unittest.TestCase):
    """
    This is the monad of the OONI nettest universe. When you write a nettest
    you will subclass this object.

    * inputs: can be set to a static set of inputs. All the tests (the methods
      starting with the "test_" prefix) will be run once per input.  At every
      run the _input_ attribute of the TestCase instance will be set to the
      value of the current iteration over inputs.  Any python iterable object
      can be set to inputs.

    * inputFile: attribute should be set to an array containing the command
      line argument that should be used as the input file. Such array looks
      like this:

          ``["commandlinearg", "c", "The description"]``

      The second value of such arrray is the shorthand for the command line
      arg.  The user will then be able to specify inputs to the test via:

          ``ooniprobe mytest.py --commandlinearg path/to/file.txt``

      or

          ``ooniprobe mytest.py -c path/to/file.txt``

    * inputProcessor: should be set to a function that takes as argument an
      open file descriptor and it will yield the input to be passed to the
      test instance.

    * name: should be set to the name of the test.

    * author: should contain the name and contact details for the test author.
      The format for such string is as follows:

          ``The Name <email@example.com>``

    * version: is the version string of the test.
    """
    name    = "I Did Not Change The Name"
    author  = "John Doe <foo@example.com>"
    version = "0.0.0"

    inputFile = None
    inputs    = [None]

    report = {}
    report['errors'] = []

    optParameters = None
    optFlags      = None
    subCommands   = None

    def setUpClass(self, *args, **kwargs):
        """
        Create a TestCase instance. This function is equivalent to '__init__'.
        To add futher setup steps before a set of tests in a TestCase instance
        run, create a function called 'setUp'.

        Class attributes, such as `report`, `optParameters`, `name`, and
        `author` should be overriden statically as class attributes in any
        subclass of :class:`ooni.nettest.TestCase`, so that the calling
        functions in ooni.runner can handle them correctly.
        """
        methodName = 'runTest'
        if kwargs:
            if 'methodName' in kwargs:
                methodName = kwargs['methodName']

        super(TestCase, self).__init__(methodName=methodName)

        #for key, value in kwargs.items():
        #    setattr(self.__class__, key, value)
        #
        #self.inputs = self.getInputs()

    def deferSetUp(self, ignored, result):
        """
        If we have the reporterFactory set we need to write the header. If
        such method is not present we will only run the test skipping header
        writing.
        """
        if result.reporterFactory.firstrun:
            d1 = result.reporterFactory.writeHeader()
            d2 = unittest.TestCase.deferSetUp(self, ignored, result)
            dl = defer.DeferredList([d1, d2])
            return dl
        else:
            return unittest.TestCase.deferSetUp(self, ignored, result)

    def _raaun(self, methodName, result):
        from twisted.internet import reactor
        method = getattr(self, methodName)
        log.debug("Running %s" % methodName)
        d = defer.maybeDeferred(
                utils.runWithWarningsSuppressed, self._getSuppress(), method)
        d.addBoth(lambda x : call.active() and call.cancel() or x)
        return d

    @staticmethod
    def inputParser(inputs):
        """Replace me with a custom function for parsing inputs."""
        return inputs

    def __input_file_processor__(self, fp):
        """
        I open :attr:inputFile if there is one, and return inputs one by one
        after stripping them of whitespace and running them through the parser
        :meth:`inputParser`.
        """
        for line in fp.readlines():
            yield self.inputParser(line.strip())
        fp.close()

    def __get_inputs__(self):
        """
        I am called from the ooni.runner and you probably should not override
        me. I gather the internal inputs from an instantiated test class and
        pass them to the rest of the runner.

        If you are looking for a way to parse inputs from inputFile, see
        :meth:`inputParser`.
        """
        processor = InputUnitProcessor(self.inputs,
                                       input_filter=None,
                                       catch_err=False)
        processed = processor.process()

        log.msg("Received direct inputs:\n%s" % inputs)
        log.debug("Our InputUnitProcessor is %s" % processor)

        #while processed is not StopIteration:
        #    self.inputs = processed
        #    yield self.inputs
        #else:
        #    if self.inputFile:

        if self.inputFile:
            try:
                fp = open(self.inputFile) ## xxx fixme:
            except Exception, e:          ## bad news to leave file
                log.err(e)                ## descriptors open
            else:
                from_file = self.__input_file_processor__(fp)
                self.inputs = itertools.chain(processor, from_file)
        elif self.inputFile is False:
            log.debug("%s specified that it doesn't need inputFile."
                      % self.__class__.__name__)
            self.inputs = processed
        else:
            raise BrokenImplementation

        return self.inputs

    def getOptions(self):
        '''
        for attr in attributes:
            if not attr.name is 'optParameters' or attr.name is 'optFlags':
                continue
            elif attr.name is 'optParameters':
                cls._optParameters = attr.object
            elif attr.name is 'optFlags':
                log.debug("Applying %s.%s = %s to data descriptor..."
                          % (cls.__name__, "_"+attr.name, attr.object))
                cls._optParameters = attr.object
            else:
                log.debug("How did we get here? attr.name = %s" % attr.name)
        '''
        if self.localOptions:
            if self.inputs[0] is not None or self.inputFile is not None:
                self.__get_inputs__()
            return self.localOptions
        else:
            raise Exception, "could not find cls.localOptions! 234"

        # if options:
        #     return options
        # else:
        #     ## is this safe to do? it might turn Hofstaeder...
        #     return self.__dict__
        ####################
        ## original return
        ####################
        #return {'inputs': self.inputs,
        #        'name': self.name,
        #        'version': self.version}

    def __repr__(self):
        return "<%s inputs=%s>" % (self.__class__, self.inputs)

<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: UTF-8
#
#    oonicli
#    *********
#
#    oonicli is the next generation ooniprober. It based off of twisted's trial
#    unit testing framework.
#
#    :copyright: (c) 2012 by Arturo Filastò
#    :license: see LICENSE for more details.
#
#    original copyright (c) by Twisted Matrix Laboratories.

import sys, os, random, gc, time, warnings

import unittest
import inspect

from ooni.input import InputUnitFactory
from ooni.reporter import ReporterFactory
from ooni.nettest import InputTestSuite
from ooni.plugoo import tests
from ooni import nettest, runner, reporter

from twisted.internet import defer
from twisted.application import app
from twisted.python import usage, reflect, failure, log
from twisted.python.filepath import FilePath
from twisted import plugin
from twisted.python.util import spewer
from twisted.python.compat import set
from twisted.trial import itrial
from twisted.trial import runner as irunner

def _parseLocalVariables(line):
    """
    Accepts a single line in Emacs local variable declaration format and
    returns a dict of all the variables {name: value}.
    Raises ValueError if 'line' is in the wrong format.

    See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
    """
    paren = '-*-'
    start = line.find(paren) + len(paren)
    end = line.rfind(paren)
    if start == -1 or end == -1:
        raise ValueError("%r not a valid local variable declaration" % (line,))
    items = line[start:end].split(';')
    localVars = {}
    for item in items:
        if len(item.strip()) == 0:
            continue
        split = item.split(':')
        if len(split) != 2:
            raise ValueError("%r contains invalid declaration %r"
                             % (line, item))
        localVars[split[0].strip()] = split[1].strip()
    return localVars

def loadLocalVariables(filename):
    """
    Accepts a filename and attempts to load the Emacs variable declarations
    from that file, simulating what Emacs does.

    See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
    """
    f = file(filename, "r")
    lines = [f.readline(), f.readline()]
    f.close()
    for line in lines:
        try:
            return _parseLocalVariables(line)
        except ValueError:
            pass
    return {}

def getTestModules(filename):
    testCaseVar = loadLocalVariables(filename).get('test-case-name', None)
    if testCaseVar is None:
        return []
    return testCaseVar.split(',')

def isTestFile(filename):
    """
    Returns true if 'filename' looks like a file containing unit tests.
    False otherwise.  Doesn't care whether filename exists.
    """
    basename = os.path.basename(filename)
    return (basename.startswith('test_')
            and os.path.splitext(basename)[1] == ('.py'))

class Options(usage.Options, app.ReactorSelectionMixin):
    synopsis = """%s [options] [[file|package|module|TestCase|testmethod]...]
    """ % (os.path.basename(sys.argv[0]),)

    longdesc = ("ooniprobe loads and executes a suite or a set of suites of"
                "network tests. These are loaded from modules, packages and"
                "files listed on the command line")

    optFlags = [["help", "h"],
                ["rterrors", "e", "realtime errors, print out tracebacks as "
                 "soon as they occur"],
                ["debug", "b", "Run tests in the Python debugger. Will load "
                 "'.pdbrc' from current directory if it exists."],
                ["debug-stacktraces", "B", "Report Deferred creation and "
                 "callback stack traces"],
                ["nopm", None, "don't automatically jump into debugger for "
                 "postmorteming of exceptions"],
                ["force-gc", None, "Have OONI run gc.collect() before and "
                 "after each test case."],
                ["unclean-warnings", None,
                 "Turn dirty reactor errors into warnings"],
                ["no-recurse", "N", "Don't recurse into packages"],
                ['help-reporters', None,
                 "Help on available output plugins (reporters)"]
                ]

    optParameters = [
        ["reportfile", "o", "report.yaml", "report file name"],
        ["logfile", "l", "test.log", "log file name"],
        ['temp-directory', None, '_ooni_temp',
         'Path to use as working directory for tests.'],
        ['reporter', None, 'default',
         'The reporter to use for this test run.  See --help-reporters for '
         'more info.']]

    compData = usage.Completions(
        optActions={"tbformat": usage.CompleteList(["plain", "emacs", "cgitb"]),
                    "logfile": usage.CompleteFiles(descr="log file name"),
                    },
        extraActions=[usage.CompleteFiles(
                "*.py", descr="file | module | package | TestCase | testMethod",
                repeat=True)],
        )

    tracer = None

    def __init__(self):
        self['test'] = None
        usage.Options.__init__(self)

    def coverdir(self):
        """
        Return a L{FilePath} representing the directory into which coverage
        results should be written.
        """
        coverdir = 'coverage'
        result = FilePath(self['temp-directory']).child(coverdir)
        print "Setting coverage directory to %s." % (result.path,)
        return result

    def opt_coverage(self):
        """
        Generate coverage information in the I{coverage} file in the
        directory specified by the I{trial-temp} option.
        """
        import trace
        self.tracer = trace.Trace(count=1, trace=0)
        sys.settrace(self.tracer.globaltrace)

    def opt_testmodule(self, filename):
        """
        Filename to grep for test cases (-*- test-case-name)
        """
        # If the filename passed to this parameter looks like a test module
        # we just add that to the test suite.
        #
        # If not, we inspect it for an Emacs buffer local variable called
        # 'test-case-name'.  If that variable is declared, we try to add its
        # value to the test suite as a module.
        #
        # This parameter allows automated processes (like Buildbot) to pass
        # a list of files to OONI with the general expectation of "these files,
        # whatever they are, will get tested"
        if not os.path.isfile(filename):
            sys.stderr.write("File %r doesn't exist\n" % (filename,))
            return

        filename = os.path.abspath(filename)
        self['test'] = filename

        if isTestFile(filename):
            self['tests'].add(filename)
        else:
            self['tests'].update(getTestModules(filename))

    def opt_spew(self):
        """
        Print an insanely verbose log of everything that happens.  Useful
        when debugging freezes or locks in complex code.
        """
        sys.settrace(spewer)

    def opt_help_reporters(self):
        synopsis = ("OONI's output can be customized using plugins called "
                    "Reporters. You can\nselect any of the following "
                    "reporters using --reporter=<foo>\n")
        print synopsis
        for p in plugin.getPlugins(itrial.IReporter):
            print '   ', p.longOpt, '\t', p.description
        print
        sys.exit(0)

    def opt_disablegc(self):
        """
        Disable the garbage collector
        """
        gc.disable()

    def opt_tbformat(self, opt):
        """
        Specify the format to display tracebacks with. Valid formats are
        'plain', 'emacs', and 'cgitb' which uses the nicely verbose stdlib
        cgitb.text function
        """
        try:
            self['tbformat'] = TBFORMAT_MAP[opt]
        except KeyError:
            raise usage.UsageError(
                "tbformat must be 'plain', 'emacs', or 'cgitb'.")

    def opt_recursionlimit(self, arg):
        """
        see sys.setrecursionlimit()
        """
        try:
            sys.setrecursionlimit(int(arg))
        except (TypeError, ValueError):
            raise usage.UsageError(
                "argument to recursionlimit must be an integer")

    def opt_without_module(self, option):
        """
        Fake the lack of the specified modules, separated with commas.
        """
        for module in option.split(","):
            if module in sys.modules:
                warnings.warn("Module '%s' already imported, "
                              "disabling anyway." % (module,),
                              category=RuntimeWarning)
            sys.modules[module] = None

    def parseArgs(self, *args):
        try:
            self['test'] = args[0]
        except:
            raise usage.UsageError("No test filename specified!")

    def postOptions(self):
        # Only load reporters now, as opposed to any earlier, to avoid letting
        # application-defined plugins muck up reactor selecting by importing
        # t.i.reactor and causing the default to be installed.
        self['reporter'] = reporter.OONIReporter

        if 'tbformat' not in self:
            self['tbformat'] = 'default'
        if self['nopm']:
            if not self['debug']:
                raise usage.UsageError("you must specify --debug when using "
                                       "--nopm ")
            failure.DO_POST_MORTEM = False

def run():
    if len(sys.argv) == 1:
        sys.argv.append("--help")
    config = Options()
    try:
        config.parseOptions()
    except usage.error, ue:
        raise SystemExit, "%s: %s" % (sys.argv[0], ue)

    file_name = os.path.abspath('nettests/simpletest.py')
    classes = runner.findTestClassesFromFile(config['test'])
    casesList, options = runner.loadTestsAndOptions(classes)
    for idx, cases in enumerate(casesList):
        orunner = runner.ORunner(cases, options[idx])
        orunner.run()

<commit_msg>Remove now unused stuff from oonicli<commit_after>#!/usr/bin/env python
# -*- coding: UTF-8
#
#    oonicli
#    *********
#
#    oonicli is the next generation ooniprober. It based off of twisted's trial
#    unit testing framework.
#
#    :copyright: (c) 2012 by Arturo Filastò
#    :license: see LICENSE for more details.
#
#    original copyright (c) by Twisted Matrix Laboratories.

import sys, os, random, gc, time, warnings

import unittest
import inspect

from ooni.input import InputUnitFactory
from ooni.reporter import ReporterFactory
from ooni.nettest import InputTestSuite
from ooni.plugoo import tests
from ooni import nettest, runner, reporter

from twisted.internet import defer
from twisted.application import app
from twisted.python import usage, reflect, failure, log
from twisted.python.filepath import FilePath
from twisted import plugin
from twisted.python.util import spewer
from twisted.python.compat import set
from twisted.trial import itrial
from twisted.trial import runner as irunner

class Options(usage.Options, app.ReactorSelectionMixin):
    synopsis = """%s [options] [[file|package|module|TestCase|testmethod]...]
    """ % (os.path.basename(sys.argv[0]),)

    longdesc = ("ooniprobe loads and executes a suite or a set of suites of"
                "network tests. These are loaded from modules, packages and"
                "files listed on the command line")

    optFlags = [["help", "h"]]

    optParameters = [
        ["reportfile", "o", "report.yaml", "report file name"],
        ["logfile", "l", "test.log", "log file name"],
        ['temp-directory', None, '_ooni_temp',
         'Path to use as working directory for tests.']
        ]

    compData = usage.Completions(
        extraActions=[usage.CompleteFiles(
                "*.py", descr="file | module | package | TestCase | testMethod",
                repeat=True)],
        )

    tracer = None

    def __init__(self):
        self['test'] = None
        usage.Options.__init__(self)

    def opt_spew(self):
        """
        Print an insanely verbose log of everything that happens.  Useful
        when debugging freezes or locks in complex code.
        """
        sys.settrace(spewer)

    def parseArgs(self, *args):
        try:
            self['test'] = args[0]
        except:
            raise usage.UsageError("No test filename specified!")

    def postOptions(self):
        self['reporter'] = reporter.OONIReporter

def run():
    if len(sys.argv) == 1:
        sys.argv.append("--help")
    config = Options()
    try:
        config.parseOptions()
    except usage.error, ue:
        raise SystemExit, "%s: %s" % (sys.argv[0], ue)

    classes = runner.findTestClassesFromFile(config['test'])
    casesList, options = runner.loadTestsAndOptions(classes)
    for idx, cases in enumerate(casesList):
        orunner = runner.ORunner(cases, options[idx])
        orunner.run()

<|endoftext|>


<commit_before>#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

# Base class for RPC testing

import logging
import optparse
import os
import sys
import shutil
import tempfile
import traceback

from .util import (
    initialize_chain,
    start_nodes,
    connect_nodes_bi,
    sync_blocks,
    sync_mempools,
    stop_nodes,
    wait_bitcoinds,
    enable_coverage,
    check_json_precision,
    initialize_chain_clean,
    PortSeed,
)
from .authproxy import JSONRPCException

class BitcoinTestFramework(object):

    def __init__(self):
        self.num_nodes = 4
        self.setup_clean_chain = False
        self.nodes = None

    def run_test(self):
        raise NotImplementedError

    def add_options(self, parser):
        pass

    def setup_chain(self):
        print("Initializing test directory "+self.options.tmpdir)
        if self.setup_clean_chain:
            initialize_chain_clean(self.options.tmpdir, self.num_nodes)
        else:
            initialize_chain(self.options.tmpdir, self.num_nodes)

    def setup_nodes(self):
        return start_nodes(self.num_nodes, self.options.tmpdir)

    def setup_network(self, split = False):
        self.nodes = self.setup_nodes()

        # Connect the nodes as a "chain".  This allows us
        # to split the network between nodes 1 and 2 to get
        # two halves that can work on competing chains.

        # If we joined network halves, connect the nodes from the joint
        # on outward.  This ensures that chains are properly reorganised.
        if not split:
            connect_nodes_bi(self.nodes, 1, 2)
            sync_blocks(self.nodes[1:3])
            sync_mempools(self.nodes[1:3])

        connect_nodes_bi(self.nodes, 0, 1)
        connect_nodes_bi(self.nodes, 2, 3)
        self.is_network_split = split
        self.sync_all()

    def split_network(self):
        """
        Split the network of four nodes into nodes 0/1 and 2/3.
        """
        assert not self.is_network_split
        stop_nodes(self.nodes)
        wait_bitcoinds()
        self.setup_network(True)

    def sync_all(self):
        if self.is_network_split:
            sync_blocks(self.nodes[:2])
            sync_blocks(self.nodes[2:])
            sync_mempools(self.nodes[:2])
            sync_mempools(self.nodes[2:])
        else:
            sync_blocks(self.nodes)
            sync_mempools(self.nodes)

    def join_network(self):
        """
        Join the (previously split) network halves together.
        """
        assert self.is_network_split
        stop_nodes(self.nodes)
        wait_bitcoinds()
        self.setup_network(False)

    def main(self):

        parser = optparse.OptionParser(usage="%prog [options]")
        parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true",
                          help="Leave bitcoinds and test.* datadir on exit or error")
        parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true",
                          help="Don't stop bitcoinds after the test execution")
        parser.add_option("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__))+"/../../../src"),
                          help="Source directory containing bitcoind/bitcoin-cli (default: %default)")
        parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
                          help="Root directory for datadirs")
        parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",
                          help="Print out all RPC calls as they are made")
        parser.add_option("--portseed", dest="port_seed", default=os.getpid(), type='int',
                          help="The seed to use for assigning port numbers (default: current process id)")
        parser.add_option("--coveragedir", dest="coveragedir",
                          help="Write tested RPC commands into this directory")
        self.add_options(parser)
        (self.options, self.args) = parser.parse_args()

        self.options.tmpdir += '/' + str(self.options.port_seed)

        if self.options.trace_rpc:
            logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)

        if self.options.coveragedir:
            enable_coverage(self.options.coveragedir)

        PortSeed.n = self.options.port_seed

        os.environ['PATH'] = self.options.srcdir+":"+self.options.srcdir+"/qt:"+os.environ['PATH']

        check_json_precision()

        success = False
        try:
            if not os.path.isdir(self.options.tmpdir):
                os.makedirs(self.options.tmpdir)
            self.setup_chain()

            self.setup_network()

            self.run_test()

            success = True

        except JSONRPCException as e:
            print("JSONRPC error: "+e.error['message'])
            traceback.print_tb(sys.exc_info()[2])
        except AssertionError as e:
            print("Assertion failed: " + str(e))
            traceback.print_tb(sys.exc_info()[2])
        except KeyError as e:
            print("key not found: "+ str(e))
            traceback.print_tb(sys.exc_info()[2])
        except Exception as e:
            print("Unexpected exception caught during testing: " + repr(e))
            traceback.print_tb(sys.exc_info()[2])
        except KeyboardInterrupt as e:
            print("Exiting after " + repr(e))

        if not self.options.noshutdown:
            print("Stopping nodes")
            stop_nodes(self.nodes)
            wait_bitcoinds()
        else:
            print("Note: bitcoinds were not stopped and may still be running")

        if not self.options.nocleanup and not self.options.noshutdown and success:
            print("Cleaning up")
            shutil.rmtree(self.options.tmpdir)
        else:
            print("Not cleaning up dir %s" % self.options.tmpdir)

        if success:
            print("Tests successful")
            sys.exit(0)
        else:
            print("Failed")
            sys.exit(1)

# Test framework for doing p2p comparison testing, which sets up some bitcoind
# binaries:
# 1 binary: test binary
# 2 binaries: 1 test binary, 1 ref binary
# n>2 binaries: 1 test binary, n-1 ref binaries

class ComparisonTestFramework(BitcoinTestFramework):

    def __init__(self):
        super().__init__()
        self.num_nodes = 2
        self.setup_clean_chain = True

    def add_options(self, parser):
        parser.add_option("--testbinary", dest="testbinary",
                          default=os.getenv("BITCOIND", "bitcoind"),
                          help="bitcoind binary to test")
        parser.add_option("--refbinary", dest="refbinary",
                          default=os.getenv("BITCOIND", "bitcoind"),
                          help="bitcoind binary to use for reference nodes (if any)")

    def setup_network(self):
        self.nodes = start_nodes(
            self.num_nodes, self.options.tmpdir,
            extra_args=[['-debug', '-whitelist=127.0.0.1']] * self.num_nodes,
            binary=[self.options.testbinary] +
            [self.options.refbinary]*(self.num_nodes-1))
<commit_msg>[qa] test_framework: Add wrapper for stop_node<commit_after>#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

# Base class for RPC testing

import logging
import optparse
import os
import sys
import shutil
import tempfile
import traceback

from .util import (
    initialize_chain,
    start_nodes,
    connect_nodes_bi,
    sync_blocks,
    sync_mempools,
    stop_nodes,
    stop_node,
    wait_bitcoinds,
    enable_coverage,
    check_json_precision,
    initialize_chain_clean,
    PortSeed,
)
from .authproxy import JSONRPCException

class BitcoinTestFramework(object):

    def __init__(self):
        self.num_nodes = 4
        self.setup_clean_chain = False
        self.nodes = None

    def run_test(self):
        raise NotImplementedError

    def add_options(self, parser):
        pass

    def setup_chain(self):
        print("Initializing test directory "+self.options.tmpdir)
        if self.setup_clean_chain:
            initialize_chain_clean(self.options.tmpdir, self.num_nodes)
        else:
            initialize_chain(self.options.tmpdir, self.num_nodes)

    def stop_node(self, num_node):
        stop_node(self.nodes[num_node], num_node)

    def setup_nodes(self):
        return start_nodes(self.num_nodes, self.options.tmpdir)

    def setup_network(self, split = False):
        self.nodes = self.setup_nodes()

        # Connect the nodes as a "chain".  This allows us
        # to split the network between nodes 1 and 2 to get
        # two halves that can work on competing chains.

        # If we joined network halves, connect the nodes from the joint
        # on outward.  This ensures that chains are properly reorganised.
        if not split:
            connect_nodes_bi(self.nodes, 1, 2)
            sync_blocks(self.nodes[1:3])
            sync_mempools(self.nodes[1:3])

        connect_nodes_bi(self.nodes, 0, 1)
        connect_nodes_bi(self.nodes, 2, 3)
        self.is_network_split = split
        self.sync_all()

    def split_network(self):
        """
        Split the network of four nodes into nodes 0/1 and 2/3.
        """
        assert not self.is_network_split
        stop_nodes(self.nodes)
        wait_bitcoinds()
        self.setup_network(True)

    def sync_all(self):
        if self.is_network_split:
            sync_blocks(self.nodes[:2])
            sync_blocks(self.nodes[2:])
            sync_mempools(self.nodes[:2])
            sync_mempools(self.nodes[2:])
        else:
            sync_blocks(self.nodes)
            sync_mempools(self.nodes)

    def join_network(self):
        """
        Join the (previously split) network halves together.
        """
        assert self.is_network_split
        stop_nodes(self.nodes)
        wait_bitcoinds()
        self.setup_network(False)

    def main(self):

        parser = optparse.OptionParser(usage="%prog [options]")
        parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true",
                          help="Leave bitcoinds and test.* datadir on exit or error")
        parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true",
                          help="Don't stop bitcoinds after the test execution")
        parser.add_option("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__))+"/../../../src"),
                          help="Source directory containing bitcoind/bitcoin-cli (default: %default)")
        parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
                          help="Root directory for datadirs")
        parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",
                          help="Print out all RPC calls as they are made")
        parser.add_option("--portseed", dest="port_seed", default=os.getpid(), type='int',
                          help="The seed to use for assigning port numbers (default: current process id)")
        parser.add_option("--coveragedir", dest="coveragedir",
                          help="Write tested RPC commands into this directory")
        self.add_options(parser)
        (self.options, self.args) = parser.parse_args()

        self.options.tmpdir += '/' + str(self.options.port_seed)

        if self.options.trace_rpc:
            logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)

        if self.options.coveragedir:
            enable_coverage(self.options.coveragedir)

        PortSeed.n = self.options.port_seed

        os.environ['PATH'] = self.options.srcdir+":"+self.options.srcdir+"/qt:"+os.environ['PATH']

        check_json_precision()

        success = False
        try:
            if not os.path.isdir(self.options.tmpdir):
                os.makedirs(self.options.tmpdir)
            self.setup_chain()

            self.setup_network()

            self.run_test()

            success = True

        except JSONRPCException as e:
            print("JSONRPC error: "+e.error['message'])
            traceback.print_tb(sys.exc_info()[2])
        except AssertionError as e:
            print("Assertion failed: " + str(e))
            traceback.print_tb(sys.exc_info()[2])
        except KeyError as e:
            print("key not found: "+ str(e))
            traceback.print_tb(sys.exc_info()[2])
        except Exception as e:
            print("Unexpected exception caught during testing: " + repr(e))
            traceback.print_tb(sys.exc_info()[2])
        except KeyboardInterrupt as e:
            print("Exiting after " + repr(e))

        if not self.options.noshutdown:
            print("Stopping nodes")
            stop_nodes(self.nodes)
            wait_bitcoinds()
        else:
            print("Note: bitcoinds were not stopped and may still be running")

        if not self.options.nocleanup and not self.options.noshutdown and success:
            print("Cleaning up")
            shutil.rmtree(self.options.tmpdir)
        else:
            print("Not cleaning up dir %s" % self.options.tmpdir)

        if success:
            print("Tests successful")
            sys.exit(0)
        else:
            print("Failed")
            sys.exit(1)

# Test framework for doing p2p comparison testing, which sets up some bitcoind
# binaries:
# 1 binary: test binary
# 2 binaries: 1 test binary, 1 ref binary
# n>2 binaries: 1 test binary, n-1 ref binaries

class ComparisonTestFramework(BitcoinTestFramework):

    def __init__(self):
        super().__init__()
        self.num_nodes = 2
        self.setup_clean_chain = True

    def add_options(self, parser):
        parser.add_option("--testbinary", dest="testbinary",
                          default=os.getenv("BITCOIND", "bitcoind"),
                          help="bitcoind binary to test")
        parser.add_option("--refbinary", dest="refbinary",
                          default=os.getenv("BITCOIND", "bitcoind"),
                          help="bitcoind binary to use for reference nodes (if any)")

    def setup_network(self):
        self.nodes = start_nodes(
            self.num_nodes, self.options.tmpdir,
            extra_args=[['-debug', '-whitelist=127.0.0.1']] * self.num_nodes,
            binary=[self.options.testbinary] +
            [self.options.refbinary]*(self.num_nodes-1))
<|endoftext|>


<commit_before>"""
Attribute types used for the model.

The types system provides a mechanism for serializing/un the data to/from JSON
structures and for capturing additional information about the model attributes.
"""
import logging
from datetime import datetime, timedelta, tzinfo, date
from collections import namedtuple
from weakref import WeakKeyDictionary, WeakValueDictionary

import pytz
from units.quantity import Quantity

import stravalib.model

META = 1
SUMMARY = 2
DETAILED = 3

class Attribute(object):
    """
    Base descriptor class for a Strava model attribute.
    """
    _type = None

    def __init__(self, type_, resource_states=None, units=None):
        self.log = logging.getLogger('{0.__module__}.{0.__name__}'.format(self.__class__))
        self.type = type_
        self.resource_states = resource_states
        self.data = WeakKeyDictionary()
        self.units = units

    def __get__(self, obj, clazz):
        if obj is not None:
            # It is being called on an object (not class)
            # This can cause infinite loops, when we're attempting to get the resource_state attribute ...
            #if hasattr(clazz, 'resource_state') \
            #   and obj.resource_state is not None \
            #   and not obj.resource_state in self.resource_states:
            #    raise AttributeError("attribute required resource state not satisfied by object")
            return self.data.get(obj)
        else:
            # Rather than return the wrapped value, return the actual descriptor object
            return self

    def __set__(self, obj, val):
        if val is not None:
            self.data[obj] = self.unmarshal(val)
        else:
            self.data[obj] = None

    @property
    def type(self):
        return self._type

    @type.setter
    def type(self, v):
        self._type = v

    def marshal(self, v):
        """
        Turn this value into format for wire (JSON).

        (By default this will just return the underlying object; subclasses
        can override for specific behaviors -- e.g. date formatting.)
        """
        return v

    def unmarshal(self, v):
        """
        Convert the value from parsed JSON structure to native python representation.

        By default this will leave the value as-is since the JSON parsing routines
        typically convert to native types. The exception may be date strings or other
        more complex types, where subclasses will override this behavior.
        """
        if self.units:
            # Note that we don't want to cast to type in this case!
            if not isinstance(v, Quantity):
                v = self.units(v)
        elif not isinstance(v, self.type):
            v = self.type(v)
        return v

class DateAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None):
        super(DateAttribute, self).__init__(date, resource_states=resource_states)

    def unmarshal(self, v):
        """
        Convert a date in "2012-12-13" format to a :class:`datetime.date` object.
        """
        if not isinstance(v, date):
            # 2012-12-13
            v = datetime.strptime(v, "%Y-%m-%d").date()
        return v

class TimestampAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None, tzinfo=pytz.utc):
        super(TimestampAttribute, self).__init__(datetime, resource_states=resource_states)
        self.tzinfo = tzinfo

    def unmarshal(self, v):
        """
        Convert a timestamp in "2012-12-13T03:43:19Z" format to a `datetime.datetime` object.
        """
        if not isinstance(v, datetime):
            # 2012-12-13T03:43:19Z
            v = datetime.strptime(v, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=self.tzinfo)
        return v

LatLon = namedtuple('LatLon', ['lat', 'lon'])

class LocationAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None):
        super(LocationAttribute, self).__init__(LatLon, resource_states=resource_states)

    def unmarshal(self, v):
        """
        """
        if not isinstance(v, LatLon):
            v = LatLon(lat=v[0], lon=v[1])
        return v

class TimezoneAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None):
        super(TimezoneAttribute, self).__init__(pytz.timezone, resource_states=resource_states)

    def unmarshal(self, v):
        """
        Convert a timestamp in format "(GMT-08:00) America/Los_Angeles" to
        a `pytz.timestamp` object.
        """
        if not isinstance(v, tzinfo):
            # (GMT-08:00) America/Los_Angeles
            tzname = v.split(' ', 1)[1]
            v = pytz.timezone(tzname)
        return v

class TimeIntervalAttribute(Attribute):
    """
    Handles time durations, assumes upstream int value in seconds.
    """
    def __init__(self, resource_states=None):
        super(TimeIntervalAttribute, self).__init__(int, resource_states=resource_states)

    def unmarshal(self, v):
        """
        Convert the value from parsed JSON structure to native python representation.

        By default this will leave the value as-is since the JSON parsing routines
        typically convert to native types. The exception may be date strings or other
        more complex types, where subclasses will override this behavior.
        """
        if not isinstance(v, timedelta):
            v = timedelta(seconds=v)
        return v

class ChoicesAttribute(Attribute):
    """
    Attribute where there are several choices the attribute may take.

    Allows conversion from the API value to a more helpful python value.
    """
    def __init__(self, *args, **kwargs):
        self.choices = kwargs.pop("choices", {})
        super(ChoicesAttribute, self).__init__(*args, **kwargs)

    def marshal(self, v):
        """
        Turn this value into API format.

        Do a reverse dictionary lookup on choices to find the original value. If
        there are no keys or too many keys for now we raise a NotImplementedError
        as marshal is not used anywhere currently. In the future we will want to
        fail gracefully.
        """
        orig = [i for i in self.choices if self.choices[i] == v]
        if len(orig) == 1:
            return orig[0]
        elif len(orig) == 0:
            # No such choice
            raise NotImplementedError("No such reverse choice {0} for field {1}.".format(v, self))
        else:
            # Too many choices. We could return one possible choice (e.g. orig[0]).
            raise NotImplementedError("Too many reverse choices {0} for value {1} for field {2}".format(orig, v, self))

    def unmarshal(self, v):
        """
        Convert the value from Strava API format to useful python representation.

        If the value does not appear in the choices attribute we log an error rather
        than raising an exception as this may be caused by a change to the API upstream
        so we want to fail gracefully.
        """
        try:
            return self.choices[v]
        except KeyError:
            self.log.warning("No such choice {0} for field {1}.".format(v, self))
            # Just return the value from the API
            return v

class EntityAttribute(Attribute):
    """
    Attribute for another entity.
    """
    _lazytype = None

    def __init__(self, *args, **kwargs):
        super(EntityAttribute, self).__init__(*args, **kwargs)
        self.bind_clients = WeakKeyDictionary()

    @property
    def type(self):
        if self._lazytype:
            clazz = getattr(stravalib.model, self._lazytype)
        else:
            clazz = self._type
        return clazz

    @type.setter
    def type(self, v):
        if isinstance(v, (str, bytes)):
            # Supporting lazy class referencing
            self._lazytype = v
        else:
            self._type = v

    def __set__(self, obj, val):
        if val is not None:
            # If the "owning" object has a bind_client set, we want to pass that
            # down into the objects we are deserializing here
            self.data[obj] = self.unmarshal(val, bind_client=getattr(obj, 'bind_client', None))
        else:
            self.data[obj] = None

    def unmarshal(self, value, bind_client=None):
        """
        Cast the specified value to the entity type.
        """
        #self.log.debug("Unmarshall {0!r}: {1!r}".format(self, value))
        if not isinstance(value, self.type):
            o = self.type()
            if bind_client is not None and hasattr(o.__class__, 'bind_client'):
                o.bind_client = bind_client

            if isinstance(value, dict):
                for (k, v) in value.items():
                    if not hasattr(o.__class__, k):
                        self.log.warning("Unable to set attribute {0} on entity {1!r}".format(k, o))
                    else:
                        #self.log.debug("Setting attribute {0} on entity {1!r}".format(k, o))
                        setattr(o, k, v)
                value = o
            else:
                raise Exception("Unable to unmarshall object {0!r}".format(value))
        return value

class EntityCollection(EntityAttribute):

    def unmarshal(self, values, bind_client=None):
        """
        Cast the list.
        """
        results = []
        for v in values:
            #print "-------- Processing value: %r" % (v,)
            entity = super(EntityCollection, self).unmarshal(v, bind_client=bind_client)
            #print "-------- Got entity: %r" % (entity,)
            results.append(entity)
        return results
<commit_msg>Fixes #78 - Adding a desc/link for resource states attrs<commit_after>"""
Attribute types used for the model.

The types system provides a mechanism for serializing/un the data to/from JSON
structures and for capturing additional information about the model attributes.
"""
import logging
from datetime import datetime, timedelta, tzinfo, date
from collections import namedtuple
from weakref import WeakKeyDictionary, WeakValueDictionary

import pytz
from units.quantity import Quantity

import stravalib.model

# Depending on the type of request, objects will be returned in meta,  summary or detailed representations. The
# representation of the returned object is indicated by the resource_state attribute.
# (For more info, see https://strava.github.io/api/)

META = 1
SUMMARY = 2
DETAILED = 3

class Attribute(object):
    """
    Base descriptor class for a Strava model attribute.
    """
    _type = None

    def __init__(self, type_, resource_states=None, units=None):
        self.log = logging.getLogger('{0.__module__}.{0.__name__}'.format(self.__class__))
        self.type = type_
        self.resource_states = resource_states
        self.data = WeakKeyDictionary()
        self.units = units

    def __get__(self, obj, clazz):
        if obj is not None:
            # It is being called on an object (not class)
            # This can cause infinite loops, when we're attempting to get the resource_state attribute ...
            #if hasattr(clazz, 'resource_state') \
            #   and obj.resource_state is not None \
            #   and not obj.resource_state in self.resource_states:
            #    raise AttributeError("attribute required resource state not satisfied by object")
            return self.data.get(obj)
        else:
            # Rather than return the wrapped value, return the actual descriptor object
            return self

    def __set__(self, obj, val):
        if val is not None:
            self.data[obj] = self.unmarshal(val)
        else:
            self.data[obj] = None

    @property
    def type(self):
        return self._type

    @type.setter
    def type(self, v):
        self._type = v

    def marshal(self, v):
        """
        Turn this value into format for wire (JSON).

        (By default this will just return the underlying object; subclasses
        can override for specific behaviors -- e.g. date formatting.)
        """
        return v

    def unmarshal(self, v):
        """
        Convert the value from parsed JSON structure to native python representation.

        By default this will leave the value as-is since the JSON parsing routines
        typically convert to native types. The exception may be date strings or other
        more complex types, where subclasses will override this behavior.
        """
        if self.units:
            # Note that we don't want to cast to type in this case!
            if not isinstance(v, Quantity):
                v = self.units(v)
        elif not isinstance(v, self.type):
            v = self.type(v)
        return v

class DateAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None):
        super(DateAttribute, self).__init__(date, resource_states=resource_states)

    def unmarshal(self, v):
        """
        Convert a date in "2012-12-13" format to a :class:`datetime.date` object.
        """
        if not isinstance(v, date):
            # 2012-12-13
            v = datetime.strptime(v, "%Y-%m-%d").date()
        return v

class TimestampAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None, tzinfo=pytz.utc):
        super(TimestampAttribute, self).__init__(datetime, resource_states=resource_states)
        self.tzinfo = tzinfo

    def unmarshal(self, v):
        """
        Convert a timestamp in "2012-12-13T03:43:19Z" format to a `datetime.datetime` object.
        """
        if not isinstance(v, datetime):
            # 2012-12-13T03:43:19Z
            v = datetime.strptime(v, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=self.tzinfo)
        return v

LatLon = namedtuple('LatLon', ['lat', 'lon'])

class LocationAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None):
        super(LocationAttribute, self).__init__(LatLon, resource_states=resource_states)

    def unmarshal(self, v):
        """
        """
        if not isinstance(v, LatLon):
            v = LatLon(lat=v[0], lon=v[1])
        return v

class TimezoneAttribute(Attribute):
    """
    """
    def __init__(self, resource_states=None):
        super(TimezoneAttribute, self).__init__(pytz.timezone, resource_states=resource_states)

    def unmarshal(self, v):
        """
        Convert a timestamp in format "(GMT-08:00) America/Los_Angeles" to
        a `pytz.timestamp` object.
        """
        if not isinstance(v, tzinfo):
            # (GMT-08:00) America/Los_Angeles
            tzname = v.split(' ', 1)[1]
            v = pytz.timezone(tzname)
        return v

class TimeIntervalAttribute(Attribute):
    """
    Handles time durations, assumes upstream int value in seconds.
    """
    def __init__(self, resource_states=None):
        super(TimeIntervalAttribute, self).__init__(int, resource_states=resource_states)

    def unmarshal(self, v):
        """
        Convert the value from parsed JSON structure to native python representation.

        By default this will leave the value as-is since the JSON parsing routines
        typically convert to native types. The exception may be date strings or other
        more complex types, where subclasses will override this behavior.
        """
        if not isinstance(v, timedelta):
            v = timedelta(seconds=v)
        return v

class ChoicesAttribute(Attribute):
    """
    Attribute where there are several choices the attribute may take.

    Allows conversion from the API value to a more helpful python value.
    """
    def __init__(self, *args, **kwargs):
        self.choices = kwargs.pop("choices", {})
        super(ChoicesAttribute, self).__init__(*args, **kwargs)

    def marshal(self, v):
        """
        Turn this value into API format.

        Do a reverse dictionary lookup on choices to find the original value. If
        there are no keys or too many keys for now we raise a NotImplementedError
        as marshal is not used anywhere currently. In the future we will want to
        fail gracefully.
        """
        orig = [i for i in self.choices if self.choices[i] == v]
        if len(orig) == 1:
            return orig[0]
        elif len(orig) == 0:
            # No such choice
            raise NotImplementedError("No such reverse choice {0} for field {1}.".format(v, self))
        else:
            # Too many choices. We could return one possible choice (e.g. orig[0]).
            raise NotImplementedError("Too many reverse choices {0} for value {1} for field {2}".format(orig, v, self))

    def unmarshal(self, v):
        """
        Convert the value from Strava API format to useful python representation.

        If the value does not appear in the choices attribute we log an error rather
        than raising an exception as this may be caused by a change to the API upstream
        so we want to fail gracefully.
        """
        try:
            return self.choices[v]
        except KeyError:
            self.log.warning("No such choice {0} for field {1}.".format(v, self))
            # Just return the value from the API
            return v

class EntityAttribute(Attribute):
    """
    Attribute for another entity.
    """
    _lazytype = None

    def __init__(self, *args, **kwargs):
        super(EntityAttribute, self).__init__(*args, **kwargs)
        self.bind_clients = WeakKeyDictionary()

    @property
    def type(self):
        if self._lazytype:
            clazz = getattr(stravalib.model, self._lazytype)
        else:
            clazz = self._type
        return clazz

    @type.setter
    def type(self, v):
        if isinstance(v, (str, bytes)):
            # Supporting lazy class referencing
            self._lazytype = v
        else:
            self._type = v

    def __set__(self, obj, val):
        if val is not None:
            # If the "owning" object has a bind_client set, we want to pass that
            # down into the objects we are deserializing here
            self.data[obj] = self.unmarshal(val, bind_client=getattr(obj, 'bind_client', None))
        else:
            self.data[obj] = None

    def unmarshal(self, value, bind_client=None):
        """
        Cast the specified value to the entity type.
        """
        #self.log.debug("Unmarshall {0!r}: {1!r}".format(self, value))
        if not isinstance(value, self.type):
            o = self.type()
            if bind_client is not None and hasattr(o.__class__, 'bind_client'):
                o.bind_client = bind_client

            if isinstance(value, dict):
                for (k, v) in value.items():
                    if not hasattr(o.__class__, k):
                        self.log.warning("Unable to set attribute {0} on entity {1!r}".format(k, o))
                    else:
                        #self.log.debug("Setting attribute {0} on entity {1!r}".format(k, o))
                        setattr(o, k, v)
                value = o
            else:
                raise Exception("Unable to unmarshall object {0!r}".format(value))
        return value

class EntityCollection(EntityAttribute):

    def unmarshal(self, values, bind_client=None):
        """
        Cast the list.
        """
        results = []
        for v in values:
            #print "-------- Processing value: %r" % (v,)
            entity = super(EntityCollection, self).unmarshal(v, bind_client=bind_client)
            #print "-------- Got entity: %r" % (entity,)
            results.append(entity)
        return results
<|endoftext|>


<commit_before>from collections import OrderedDict
import inspect

from qstrader.broker.portfolio.position import Position

class PositionHandler(object):
    """
    A class that keeps track of, and updates, the current
    list of Position instances stored in a Portfolio entity.
    """

    def __init__(self):
        """
        Initialise the PositionHandler object to generate
        an ordered dictionary containing the current positions.
        """
        self.positions = OrderedDict()

    def _check_set_position(self, asset):
        """
        Checks if a position exists in the positions list
        and if not creates a new key, and Position instance
        for this particular asset.
        """
        if asset in self.positions:
            position = self.positions[asset]
        else:
            position = Position(asset)
            self.positions[asset] = position
        return position

    def transact_position(self, transaction):
        """
        Execute the transaction and update the appropriate
        position for the transaction's asset accordingly.
        """
        position = self._check_set_position(transaction.asset)
        position.update(transaction)

        # If the position has zero quantity remove it
        if position.quantity == 0.0:
            del self.positions[transaction.asset]

    def update_position(
        self,
        asset,
        quantity=None,
        current_price=None,
        current_dt=None,
        book_cost_pu=None
    ):
        """
        Update the attributes of a particular Position
        via its Asset.
        """
        position = self._check_set_position(asset)

        # Update each position attribute if
        # the parameters aren't None
        frame = inspect.currentframe()
        varvals = inspect.getargvalues(frame)
        for attr in (
            'quantity', 'current_price',
            'current_dt', 'book_cost_pu'
        ):
            if varvals.locals[attr] is not None:
                setattr(position, attr, varvals.locals[attr])

    def update_commission(self, asset, commission):
        """
        Adjust the book cost per unit of the Position
        instance for this particular Asset.
        """
        if asset in self.positions:
            self.positions[asset].update_book_cost_for_commission(
                asset, commission
            )

    def total_non_cash_book_cost(self):
        """
        Calculate the sum of all the Positions' book costs
        excluding cash.
        """
        return sum(
            pos.book_cost
            for asset, pos in self.positions.items()
        )

    def total_book_cost(self):
        """
        Calculate the sum of all the Positions' book cost
        including cash.
        """
        return sum(
            pos.book_cost if not asset.startswith('CASH') else pos.market_value
            for asset, pos in self.positions.items()
        )

    def total_non_cash_market_value(self):
        """
        Calculate the sum of all the positions' market values
        excluding cash.
        """
        return sum(
            0.0 if asset.startswith('CASH') else pos.market_value
            for asset, pos in self.positions.items()
        )

    def total_market_value(self):
        """
        Calculate the sum of all the positions' market values
        including cash.
        """
        return sum(
            pos.market_value
            for asset, pos in self.positions.items()
        )

    def total_non_cash_unrealised_gain(self):
        """
        Calculate the sum of all the positions'
        unrealised gains excluding cash.
        """
        return sum(
            pos.unrealised_gain
            for asset, pos in self.positions.items()
        )

    def total_unrealised_gain(self):
        """
        Calculate the sum of all the positions'
        unrealised gains including cash.
        """
        return sum(
            0.0 if asset.startswith('CASH') else pos.unrealised_gain
            for asset, pos in self.positions.items()
        )

    def total_non_cash_unrealised_percentage_gain(self):
        """
        Calculate the total unrealised percentage gain
        on the positions excluding cash.
        """
        tbc = self.total_non_cash_book_cost()
        if tbc == 0.0:
            return 0.0
        return (self.total_non_cash_market_value() - tbc) / tbc * 100.0

    def total_unrealised_percentage_gain(self):
        """
        Calculate the total unrealised percentage gain
        on the positions including cash.
        """
        tbc = self.total_book_cost()
        if tbc == 0.0:
            return 0.0
        return (self.total_market_value() - tbc) / tbc * 100.0
<commit_msg>Refactored PositionHandler to remove 'inspect' module and replace with specific tests for attributes.<commit_after>from collections import OrderedDict

from qstrader.broker.portfolio.position import Position

class PositionHandler(object):
    """
    A class that keeps track of, and updates, the current
    list of Position instances stored in a Portfolio entity.
    """

    def __init__(self):
        """
        Initialise the PositionHandler object to generate
        an ordered dictionary containing the current positions.
        """
        self.positions = OrderedDict()

    def _check_set_position(self, asset):
        """
        Checks if a position exists in the positions list
        and if not creates a new key, and Position instance
        for this particular asset.
        """
        if asset in self.positions:
            position = self.positions[asset]
        else:
            position = Position(asset)
            self.positions[asset] = position
        return position

    def transact_position(self, transaction):
        """
        Execute the transaction and update the appropriate
        position for the transaction's asset accordingly.
        """
        position = self._check_set_position(transaction.asset)
        position.update(transaction)

        # If the position has zero quantity remove it
        if position.quantity == 0.0:
            del self.positions[transaction.asset]

    def update_position(
        self,
        asset,
        quantity=None,
        current_price=None,
        current_dt=None,
        book_cost_pu=None
    ):
        """
        Update the attributes of a particular Position
        via its Asset.
        """
        position = self._check_set_position(asset)

        if quantity is not None:
            position.quantity = quantity
        if current_price is not None:
            position.current_price = current_price
        if current_dt is not None:
            position.current_dt = current_dt
        if book_cost_pu is not None:
            position.book_cost_pu = book_cost_pu

    def update_commission(self, asset, commission):
        """
        Adjust the book cost per unit of the Position
        instance for this particular Asset.
        """
        if asset in self.positions:
            self.positions[asset].update_book_cost_for_commission(
                asset, commission
            )

    def total_non_cash_book_cost(self):
        """
        Calculate the sum of all the Positions' book costs
        excluding cash.
        """
        return sum(
            pos.book_cost
            for asset, pos in self.positions.items()
        )

    def total_book_cost(self):
        """
        Calculate the sum of all the Positions' book cost
        including cash.
        """
        return sum(
            pos.book_cost if not asset.startswith('CASH') else pos.market_value
            for asset, pos in self.positions.items()
        )

    def total_non_cash_market_value(self):
        """
        Calculate the sum of all the positions' market values
        excluding cash.
        """
        return sum(
            0.0 if asset.startswith('CASH') else pos.market_value
            for asset, pos in self.positions.items()
        )

    def total_market_value(self):
        """
        Calculate the sum of all the positions' market values
        including cash.
        """
        return sum(
            pos.market_value
            for asset, pos in self.positions.items()
        )

    def total_non_cash_unrealised_gain(self):
        """
        Calculate the sum of all the positions'
        unrealised gains excluding cash.
        """
        return sum(
            pos.unrealised_gain
            for asset, pos in self.positions.items()
        )

    def total_unrealised_gain(self):
        """
        Calculate the sum of all the positions'
        unrealised gains including cash.
        """
        return sum(
            0.0 if asset.startswith('CASH') else pos.unrealised_gain
            for asset, pos in self.positions.items()
        )

    def total_non_cash_unrealised_percentage_gain(self):
        """
        Calculate the total unrealised percentage gain
        on the positions excluding cash.
        """
        tbc = self.total_non_cash_book_cost()
        if tbc == 0.0:
            return 0.0
        return (self.total_non_cash_market_value() - tbc) / tbc * 100.0

    def total_unrealised_percentage_gain(self):
        """
        Calculate the total unrealised percentage gain
        on the positions including cash.
        """
        tbc = self.total_book_cost()
        if tbc == 0.0:
            return 0.0
        return (self.total_market_value() - tbc) / tbc * 100.0
<|endoftext|>


<commit_before># Copyright 2011-2012 10gen, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for SSL support."""

import unittest
import sys
sys.path[0:0] = [""]

from nose.plugins.skip import SkipTest

from pymongo import Connection, ReplicaSetConnection
from pymongo.errors import ConfigurationError, ConnectionFailure

have_ssl = True
try:
    import ssl
except ImportError:
    have_ssl = False

class TestSSL(unittest.TestCase):

    def setUp(self):
        pass

    def test_config_ssl(self):
        self.assertRaises(ConfigurationError, Connection, ssl='foo')
        self.assertRaises(TypeError, Connection, ssl=0)
        self.assertRaises(TypeError, Connection, ssl=5.5)
        self.assertRaises(TypeError, Connection, ssl=[])

        self.assertRaises(ConfigurationError, ReplicaSetConnection, ssl='foo')
        self.assertRaises(TypeError, ReplicaSetConnection, ssl=0)
        self.assertRaises(TypeError, ReplicaSetConnection, ssl=5.5)
        self.assertRaises(TypeError, ReplicaSetConnection, ssl=[])

    def test_no_ssl(self):
        if have_ssl:
            raise SkipTest(
                "The ssl module is available, can't test what happens "
                "without it."
            )

        self.assertRaises(ConfigurationError,
                          Connection, ssl=True)
        self.assertRaises(ConfigurationError,
                          ReplicaSetConnection, ssl=True)

    def test_simple_ops(self):
        if not have_ssl:
            raise SkipTest("The ssl module is not available.")

        try:
            conn = Connection(connectTimeoutMS=100, ssl=True)
        # MongoDB not configured for SSL?
        except ConnectionFailure:
            raise SkipTest("No mongod available over SSL")
        response = conn.admin.command('ismaster')
        if 'setName' in response:
            conn = ReplicaSetConnection(replicaSet=response['setName'],
                                        w=len(response['hosts']),
                                        ssl=True)

        db = conn.pymongo_ssl_test
        db.test.drop()
        self.assertTrue(db.test.insert({'ssl': True}, safe=True))
        self.assertTrue(db.test.find_one()['ssl'])

if __name__ == "__main__":
    unittest.main()
<commit_msg>Skip SSL tests on broken old python 3 versions.<commit_after># Copyright 2011-2012 10gen, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for SSL support."""

import unittest
import sys
sys.path[0:0] = [""]

from nose.plugins.skip import SkipTest

from pymongo import Connection, ReplicaSetConnection
from pymongo.errors import ConfigurationError, ConnectionFailure

have_ssl = True
try:
    import ssl
except ImportError:
    have_ssl = False

class TestSSL(unittest.TestCase):

    def setUp(self):
        if sys.version.startswith('3.0'):
            raise SkipTest("Python 3.0.x has problems "
                           "with SSL and socket timeouts.")

    def test_config_ssl(self):
        self.assertRaises(ConfigurationError, Connection, ssl='foo')
        self.assertRaises(TypeError, Connection, ssl=0)
        self.assertRaises(TypeError, Connection, ssl=5.5)
        self.assertRaises(TypeError, Connection, ssl=[])

        self.assertRaises(ConfigurationError, ReplicaSetConnection, ssl='foo')
        self.assertRaises(TypeError, ReplicaSetConnection, ssl=0)
        self.assertRaises(TypeError, ReplicaSetConnection, ssl=5.5)
        self.assertRaises(TypeError, ReplicaSetConnection, ssl=[])

    def test_no_ssl(self):
        if have_ssl:
            raise SkipTest(
                "The ssl module is available, can't test what happens "
                "without it."
            )

        self.assertRaises(ConfigurationError,
                          Connection, ssl=True)
        self.assertRaises(ConfigurationError,
                          ReplicaSetConnection, ssl=True)

    def test_simple_ops(self):
        if not have_ssl:
            raise SkipTest("The ssl module is not available.")

        try:
            conn = Connection(connectTimeoutMS=100, ssl=True)
        # MongoDB not configured for SSL?
        except ConnectionFailure:
            raise SkipTest("No mongod available over SSL")
        response = conn.admin.command('ismaster')
        if 'setName' in response:
            conn = ReplicaSetConnection(replicaSet=response['setName'],
                                        w=len(response['hosts']),
                                        ssl=True)

        db = conn.pymongo_ssl_test
        db.test.drop()
        self.assertTrue(db.test.insert({'ssl': True}, safe=True))
        self.assertTrue(db.test.find_one()['ssl'])

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