Example #1
0
class Version(TCMS):
    """ Product version """

    # Local cache of Version
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["name", "product"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Version Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Version id")
    name = property(_getter("name"), doc="Version name")
    product = property(_getter("product"), doc="Version product")

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """

        # Search cache by the version name and product
        if "product" in kwargs and ("version" in kwargs or "name" in kwargs):
            product = kwargs.get("product")
            if isinstance(product, Product):
                product = product.name
            name = kwargs.get("name", kwargs.get("version"))
            return cls._cache["{0}---in---{1}".format(name, product)], name

        # Default search by id otherwise
        return super(Version, cls)._cache_lookup(id, **kwargs)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Version Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None, product=None, **kwargs):
        """ Initialize by version id or product and version """

        # Backward compatibility for 'version' argument (now called 'name')
        name = name if name is not None else kwargs.get("version")

        # Initialize (unless already done)
        id, ignore, inject, initialized = self._is_initialized(id)
        if initialized:
            return
        TCMS.__init__(self, id)

        # If inject given, fetch tag data from it
        if inject:
            self._fetch(inject)
        # Initialize by version name and product
        elif name is not None and product is not None:
            self._name = name
            # Convert product into object if necessary
            if isinstance(product, Product):
                self._product = product
            else:
                self._product = Product(product)
            # Index by name/product (but only when the product name is known)
            if self.product._name is not TCMSNone:
                self._index("{0}---in---{1}".format(self.name,
                                                    self.product.name))
        # Otherwise just make sure the version id was provided
        elif not id:
            raise TCMSError(
                "Need either version id or both product "
                "and version name to initialize the Version object.")

    def __str__(self):
        """ Version name for printing """
        return self.name

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Version Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Fetch version data from the server """
        TCMS._fetch(self, inject)

        # Directly fetch from the initial object dict
        if inject is not None:
            log.debug("Processing Version ID#{0} inject".format(inject["id"]))
        # Search by version id
        elif self._id is not TCMSNone:
            try:
                log.info("Fetching version {0}".format(self.identifier))
                inject = self._server.Product.filter_versions({'id':
                                                               self.id})[0]
            except IndexError:
                raise TCMSError("Cannot find version for {0}".format(
                    self.identifier))
        # Search by product and name
        else:
            try:
                log.info("Fetching version '{0}' of '{1}'".format(
                    self.name, self.product.name))
                inject = self._server.Product.filter_versions({
                    'product':
                    self.product.id,
                    'value':
                    self.name
                })[0]
            except IndexError:
                raise TCMSError("Cannot find version for '{0}'".format(
                    self.name))
        # Initialize data from the inject and index into cache
        log.debug("Initializing Version ID#{0}".format(inject["id"]))
        log.data(pretty(inject))
        self._inject = inject
        self._id = inject["id"]
        self._name = inject["value"]
        self._product = Product(inject["product_id"])
        # Index by product name & version name (if product is cached)
        if self.product._name is not TCMSNone:
            self._index("{0}---in---{1}".format(self.name, self.product.name))
        # Otherwise index by id only
        else:
            self._index()
Example #2
0
class Product(TCMS):
    """ Product """

    # Local cache of Product
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["name"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Product Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Product id")
    name = property(_getter("name"), doc="Product name")

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """
        # Search the cache by product name
        if "name" in kwargs:
            name = kwargs.get("name")
            return cls._cache[name], name

        return super(Product, cls)._cache_lookup(id, **kwargs)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Product Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None):
        """
        Initialize the Product by id or name

        Examples:

        Product(60)
        Product(id=60)
        Product("Red Hat Enterprise Linux 6")
        Product(name="Red Hat Enterprise Linux 6")
        """

        # Initialize (unless already done)
        id, name, inject, initialized = self._is_initialized(id or name)
        if initialized:
            return
        TCMS.__init__(self, id)

        # If inject given, fetch test case data from it
        if inject:
            self._fetch(inject)
        # Initialize by name
        elif name is not None:
            self._name = name
            self._index(name)
        # Otherwise just check that the product id was provided
        elif not id:
            raise TCMSError("Need id or name to initialize Product")

    def __str__(self):
        """ Product name for printing """
        return self.name

    @staticmethod
    def search(**query):
        """ Search for products """
        return [
            Product(hash["id"])
            for hash in TCMS()._server.Product.filter(dict(query))
        ]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Product Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Fetch product data from the server """
        TCMS._fetch(self, inject)

        # Directly fetch from the initial object dict
        if inject is not None:
            log.debug("Initializing Product ID#{0}".format(inject["id"]))
            log.data(pretty(inject))
            self._id = inject["id"]
            self._name = inject["name"]
        # Search by product id
        elif self._id is not TCMSNone:
            try:
                log.info("Fetching product " + self.identifier)
                inject = self._server.Product.filter({'id': self.id})[0]
                log.debug("Initializing product " + self.identifier)
                log.data(pretty(inject))
                self._inject = inject
                self._name = inject["name"]
            except IndexError:
                raise TCMSError("Cannot find product for " + self.identifier)
        # Search by product name
        else:
            try:
                log.info("Fetching product '{0}'".format(self.name))
                inject = self._server.Product.filter({'name': self.name})[0]
                log.debug("Initializing product '{0}'".format(self.name))
                log.data(pretty(inject))
                self._inject = inject
                self._id = inject["id"]
            except IndexError:
                raise TCMSError("Cannot find product for '{0}'".format(
                    self.name))
        # Index the fetched object into cache
        self._index(self.name)
Example #3
0
class User(TCMS):
    """ User """

    # Local cache of User objects indexed by user id
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["name", "login", "email"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  User Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="User id.")
    login = property(_getter("login"), doc="Login username.")
    email = property(_getter("email"), doc="User email address.")
    name = property(_getter("name"), doc="User first name and last name.")

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  User Decorated
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """
        # Return current user
        if id is None and 'login' not in kwargs and 'email' not in kwargs:
            return cls._cache["i-am-current-user"], "current user"
        # Search by login & email
        if "login" in kwargs:
            return cls._cache[kwargs["login"]], kwargs["login"]
        if "email" in kwargs:
            return cls._cache[kwargs["email"]], kwargs["email"]
        # Default search by id
        return super(User, cls)._cache_lookup(id, **kwargs)

    @staticmethod
    def search(**query):
        """ Search for users """
        return [User(hash) for hash in TCMS()._server.User.filter(dict(query))]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  User Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __new__(cls, id=None, *args, **kwargs):
        """ Create a new object, handle caching if enabled """
        # Convert login or email into name for better logging
        if "login" in kwargs or "email" in kwargs:
            name = kwargs.get("login", kwargs.get("email"))
            return TCMS.__new__(cls, id=id, name=name, *args, **kwargs)
        else:
            return TCMS.__new__(cls, id=id, *args, **kwargs)

    def __init__(self, id=None, login=None, email=None):
        """
        Initialize by user id, login or email

        Defaults to the current user if no id, login or email provided.
        If xmlrpc initial object dict provided as the first argument,
        data are initialized directly from it.
        """

        # Initialize (unless already done)
        id, name, inject, initialized = self._is_initialized(id or login
                                                             or email)
        if initialized:
            return
        TCMS.__init__(self, id, prefix="UID")

        # If inject given, fetch data from it
        if inject:
            self._fetch(inject)
        # Otherwise initialize by login or email
        elif name is not None:
            if "@" in name:
                self._email = name
            else:
                self._login = name
            self._index(name)

    def __str__(self):
        """ User login for printing """
        return self.name if self.name is not None else "No Name"

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  User Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Fetch user data from the server """
        TCMS._fetch(self, inject)

        if inject is None:
            # Search by id
            if self._id is not TCMSNone:
                try:
                    log.info("Fetching user " + self.identifier)
                    inject = self._server.User.filter({"id": self.id})[0]
                except IndexError:
                    raise TCMSError("Cannot find user for " + self.identifier)
            # Search by login
            elif self._login is not TCMSNone:
                try:
                    log.info("Fetching user for login '{0}'".format(
                        self.login))
                    inject = self._server.User.filter({"username":
                                                       self.login})[0]
                except IndexError:
                    raise TCMSError("No user found for login '{0}'".format(
                        self.login))
            # Search by email
            elif self._email is not TCMSNone:
                try:
                    log.info("Fetching user for email '{0}'".format(
                        self.email))
                    inject = self._server.User.filter({"email": self.email})[0]
                except IndexError:
                    raise TCMSError("No user found for email '{0}'".format(
                        self.email))
            # Otherwise initialize to the current user
            else:
                log.info("Fetching the current user")
                inject = self._server.User.get()
                self._index("i-am-current-user")

        # Initialize data from the inject and index into cache
        log.debug("Initializing user UID#{0}".format(inject["id"]))
        log.data(pretty(inject))
        self._inject = inject
        self._id = inject["id"]
        self._login = inject["username"]
        self._email = inject["email"]
        if inject["first_name"] and inject["last_name"]:
            self._name = inject["first_name"] + " " + inject["last_name"]
        else:
            self._name = None
        self._index(self.login, self.email)
Example #4
0
class PlanType(TCMS):
    """ Plan type """

    # Local cache of PlanType objects indexed by plan type id
    _cache = {}

    # By default we cache PlanType objects for ever
    _expiration = config.NEVER_EXPIRE

    # List of all object attributes (used for init & expiration)
    _attributes = ["name"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  PlanType Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Test plan type id")
    name = property(_getter("name"), doc="Test plan type name")

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  PlanType Decorated
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """
        # Search cache by plan type name
        if "name" in kwargs:
            return cls._cache[kwargs["name"]], kwargs["name"]
        # Othewise perform default search by id
        return super(PlanType, cls)._cache_lookup(id, **kwargs)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  PlanType Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None):
        """ Initialize by test plan type id or name """

        # Initialize (unless already done)
        id, name, inject, initialized = self._is_initialized(id or name)
        if initialized:
            return
        TCMS.__init__(self, id)

        # If inject given, fetch data from it
        if inject:
            self._fetch(inject)
        # Initialize by name
        elif name is not None:
            self._name = name
            self._index(name)
        # Otherwise just check that the test plan type id was provided
        elif not id:
            raise TCMSError(
                "Need either id or name to initialize the PlanType object")

    def __str__(self):
        """ PlanType name for printing """
        return self.name

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  PlanType Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Get the missing test plan type data """
        TCMS._fetch(self, inject)

        # Directly fetch from the initial object dict
        if inject is not None:
            log.info("Processing PlanType ID#{0} inject".format(inject["id"]))
        # Search by test plan type id
        elif self._id is not TCMSNone:
            try:
                log.info("Fetching test plan type " + self.identifier)
                inject = self._server.TestPlan.get_plan_type(self.id)
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("Cannot find test plan type for " +
                                self.identifier)
        # Search by test plan type name
        else:
            try:
                log.info("Fetching test plan type '{0}'".format(self.name))
                inject = self._server.TestPlan.check_plan_type(self.name)
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("PlanType '{0}' not found".format(self.name))
        # Initialize data from the inject and index into cache
        log.debug("Initializing PlanType ID#{0}".format(inject["id"]))
        log.data(pretty(inject))
        self._inject = inject
        self._id = inject["id"]
        self._name = inject["name"]
        self._index(self.name)
Example #5
0
class Build(TCMS):
    """ Product build """

    # Local cache of Build
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["name", "product"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Build Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Build id.")
    name = property(_getter("name"), doc="Build name.")
    product = property(_getter("product"), doc="Relevant product.")

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """

        # Name and product check
        if "product" in kwargs and ("name" in kwargs or "build" in kwargs):
            product = kwargs.get("product")
            if isinstance(product, Product):
                product = product.name
            name = kwargs.get("name", kwargs.get("build"))
            return cls._cache["{0}---in---{1}".format(name, product)], name

        return super(Build, cls)._cache_lookup(id, **kwargs)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Build Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None, product=None, **kwargs):
        """ Initialize by build id or product and build name """

        # Backward compatibility for 'build' argument (now called 'name')
        name = name if name is not None else kwargs.get("build")

        # Initialize (unless already done)
        id, ignore, inject, initialized = self._is_initialized(id or name)
        if initialized:
            return
        TCMS.__init__(self, id)

        # If inject given, fetch build data from it
        if inject:
            self._fetch(inject)
        # Initialized by build name and product
        elif name is not None and product is not None:
            self._name = name
            # Detect product format
            if isinstance(product, Product):
                self._product = product
            else:
                self._product = Product(product)
            # Index by name-product (only when the product name is known)
            if self.product._name is not TCMSNone:
                self._index("{0}---in---{1}".format(self.name,
                                                    self.product.name))
        # Otherwise just check that the id was provided
        elif not id:
            raise TCMSError("Need either build id or both build name "
                            "and product to initialize the Build object.")

    def __str__(self):
        """ Build name for printing """
        return self.name

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Build Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Get the missing build data """
        TCMS._fetch(self, inject)
        # Directly fetch from the initial object dict
        if inject is not None:
            log.info("Processing build ID#{0} inject".format(
                inject["build_id"]))
        # Search by build id
        elif self._id is not TCMSNone:
            try:
                log.info("Fetching build " + self.identifier)
                inject = self._server.Build.get(self.id)
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("Cannot find build for " + self.identifier)
        # Search by build name and product
        else:
            try:
                log.info("Fetching build '{0}' of '{1}'".format(
                    self.name, self.product.name))
                inject = self._server.Build.check_build(
                    self.name, self.product.id)
                self._id = inject["build_id"]
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("Build '{0}' not found in '{1}'".format(
                    self.name, self.product.name))
            except KeyError:
                if "args" in inject:
                    log.debug(inject["args"])
                raise TCMSError("Build '{0}' not found in '{1}'".format(
                    self.name, self.product.name))

        # Initialize data from the inject and index into cache
        log.debug("Initializing Build ID#{0}".format(inject["build_id"]))
        log.data(pretty(inject))
        self._inject = inject
        self._id = inject["build_id"]
        self._name = inject["name"]
        self._product = Product({
            "id": inject["product_id"],
            "name": inject["product"]
        })
        self._index("{0}---in---{1}".format(self.name, self.product.name))
Example #6
0
class Tag(TCMS):
    """ Tag Class """

    # List of all object attributes (used for init & expiration)
    _attributes = ["name"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Tag Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Tag id")
    name = property(_getter("name"), doc="Tag name")

    # Local cache for Tag
    _cache = {}

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Tag Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None):
        """ Initialize by tag id or tag name """

        # Initialize (unless already done)
        id, name, inject, initialized = self._is_initialized(id or name)
        if initialized:
            return
        TCMS.__init__(self, id)

        # If inject given, fetch tag data from it
        if inject:
            self._fetch(inject)
        # Initialize by name
        elif name is not None:
            self._name = name
            self._index(name)
        # Otherwise just check that the tag name or id was provided
        elif not id:
            raise TCMSError("Need either tag id or tag name "
                            "to initialize the Tag object.")

    def __str__(self):
        """ Tag name for printing """
        return self.name

    def __hash__(self):
        """ Use tag name for hashing """
        # This is necessary until BZ#1084301 is fixed
        return hash(self.name)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Tag Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Fetch tag data from the server """
        TCMS._fetch(self, inject)

        # Directly fetch from the initial object dict
        if inject is not None:
            log.debug("Initializing Tag ID#{0}".format(inject["id"]))
            log.data(pretty(inject))
            self._id = inject["id"]
            self._name = inject["name"]
        # Search by tag id
        elif self._id is not TCMSNone:
            try:
                log.info("Fetching tag " + self.identifier)
                inject = self._server.Tag.get_tags({'ids': [self.id]})
                log.debug("Initializing tag " + self.identifier)
                log.data(pretty(inject))
                self._inject = inject
                self._name = inject[0]["name"]
            except IndexError:
                raise TCMSError("Cannot find tag for {0}".format(
                    self.identifier))
        # Search by tag name
        else:
            try:
                log.info("Fetching tag '{0}'".format(self.name))
                inject = self._server.Tag.get_tags({'names': [self.name]})
                log.debug("Initializing tag '{0}'".format(self.name))
                log.data(pretty(inject))
                self._inject = inject
                self._id = inject[0]["id"]
            except IndexError:
                raise TCMSError("Cannot find tag '{0}'".format(self.name))
        # Index the fetched object into cache
        self._index(self.name)
Example #7
0
class Category(TCMS):
    """ Test case category """

    # Local cache of Category objects indexed by category id
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["name", "product", "description"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Category Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Category id.")
    name = property(_getter("name"), doc="Category name.")
    product = property(_getter("product"), doc="Relevant product.")
    description = property(_getter("description"), doc="Category description.")

    @property
    def synopsis(self):
        """ Short category summary (including product info) """
        return "{0}, {1}".format(self.name, self.product)

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """

        # Name and product check
        if "product" in kwargs and ("name" in kwargs or "category" in kwargs):
            product = kwargs.get("product")
            if isinstance(product, Product):
                product = product.name
            name = kwargs.get("name", kwargs.get("category"))
            return cls._cache["{0}---in---{1}".format(name, product)], name

        return super(Category, cls)._cache_lookup(id, **kwargs)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Category Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None, product=None, **kwargs):
        """ Initialize by category id or category name and product """

        # Backward compatibility for 'category' argument (now called 'name')
        name = name if name is not None else kwargs.get("category")

        # Initialize (unless already done)
        id, ignore, inject, initialized = self._is_initialized(id or name)
        if initialized:
            return
        TCMS.__init__(self, id)

        # If inject given, fetch tag data from it
        if inject:
            self._fetch(inject)
        # Initialized by category name and product
        elif name is not None and product is not None:
            self._name = name
            # Detect product format
            if isinstance(product, Product):
                self._product = product
            else:
                self._product = Product(product)
            # Index by name-product (only when the product name is known)
            if self.product._name is not TCMSNone:
                self._index("{0}---in---{1}".format(self.name,
                                                    self.product.name))
        # Otherwise just check that the id was provided
        elif not id:
            raise TCMSError(
                "Need either category id or both category "
                "name and product to initialize the Category object.")

    def __str__(self):
        """ Category name for printing """
        return self.name

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Category Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Get the missing category data """
        TCMS._fetch(self, inject)

        # Directly fetch from the initial object dict
        if inject is not None:
            log.info("Processing category ID#{0} inject".format(inject["id"]))
        # Search by category id
        elif self._id is not TCMSNone:
            try:
                log.info("Fetching category {0}".format(self.identifier))
                inject = self._server.Product.get_category(self.id)
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("Cannot find category for " + self.identifier)
        # Search by category name and product
        else:
            try:
                log.info("Fetching category '{0}' of '{1}'".format(
                    self.name, self.product.name))
                inject = self._server.Product.check_category(
                    self.name, self.product.id)
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("Category '{0}' not found in"
                                " '{1}'".format(self.name, self.product.name))

        # Initialize data from the inject and index into cache
        log.debug("Initializing category ID#{0}".format(inject["id"]))
        log.data(pretty(inject))
        self._inject = inject
        self._id = inject["id"]
        self._name = inject["name"]
        self._product = Product({
            "id": inject["product_id"],
            "name": inject["product"]
        })
        self._index("{0}---in---{1}".format(self.name, self.product.name))
Example #8
0
class Component(TCMS):
    """ Test case component """

    # Local cache of Component objects indexed by component id plus
    # additionaly by name-in-product pairs
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["name", "product"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Component Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Component id.")
    name = property(_getter("name"), doc="Component name.")
    product = property(_getter("product"), doc="Relevant product.")

    @property
    def synopsis(self):
        """ Short component summary (including product info) """
        return "{0}, {1}".format(self.name, self.product)

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """

        # Name and product check
        if 'product' in kwargs and 'name' in kwargs:
            product = kwargs.get("product")
            if isinstance(product, Product):
                product = product.name
            name = kwargs.get("name")
            return cls._cache["{0}---in---{1}".format(name, product)], name

        return super(Component, cls)._cache_lookup(id, **kwargs)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Component Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None, product=None, **kwargs):
        """ Initialize by component id or component name and product """

        # Initialize (unless already done)
        id, ignore, inject, initialized = self._is_initialized(id)
        if initialized:
            return
        TCMS.__init__(self, id)

        # If inject given, fetch component data from it
        if inject:
            self._fetch(inject)
        # Initialized by product and component name
        elif name is not None and product is not None:
            # Detect product format
            if isinstance(product, Product):
                self._product = product
            else:
                self._product = Product(product)
            self._name = name
            # Index by name-product (only when the product name is known)
            if self.product._name is not TCMSNone:
                self._index("{0}---in---{1}".format(self.name,
                                                    self.product.name))
        # Otherwise just check that the id was provided
        elif id is None:
            raise TCMSError(
                "Need either component id or both product "
                "and component name to initialize the Component object.")

    def __str__(self):
        """ Component name for printing """
        return self.name

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Component Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Get the missing component data """
        TCMS._fetch(self, inject)

        # Directly fetch from the initial object dict
        if inject is not None:
            log.info("Processing component ID#{0} inject".format(inject["id"]))
        # Search by component id
        elif self._id is not TCMSNone:
            try:
                log.info("Fetching component " + self.identifier)
                inject = self._server.Product.get_component(self.id)
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("Cannot find component for " + self.identifier)
        # Search by component name and product
        else:
            try:
                log.info("Fetching component '{0}' of '{1}'".format(
                    self.name, self.product.name))
                inject = self._server.Product.check_component(
                    self.name, self.product.id)
            except xmlrpc.client.Fault as error:
                log.debug(error)
                raise TCMSError("Component '{0}' not found in"
                                " '{1}'".format(self.name, self.product.name))

        # Initialize data from the inject and index into cache
        log.debug("Initializing component ID#{0}".format(inject["id"]))
        log.data(pretty(inject))
        self._inject = inject
        self._id = inject["id"]
        self._name = inject["name"]
        self._product = Product({
            "id": inject["product_id"],
            "name": inject["product"]
        })
        self._index("{0}---in---{1}".format(self.name, self.product.name))

    @staticmethod
    def search(**query):
        """ Search for components """
        return [
            Component(hash)
            for hash in TCMS()._server.Product.filter_components(dict(query))
        ]
Example #9
0
class Bug(TCMS):
    """ Bug related to a test case or a case run """

    # Local cache of Bug objects indexed by internal bug id
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["bug", "system", "testcase", "caserun"]

    # Prefixes for bug systems, identifier width
    _prefixes = {1: "BZ"}
    _identifier_width = 7

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Bug Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Bug id (internal).")
    bug = property(_getter("bug"), doc="Bug (external id).")
    system = property(_getter("system"), doc="Bug system.")
    testcase = property(_getter("testcase"), doc="Test case.")
    caserun = property(_getter("caserun"), doc="Case run.")

    @property
    def synopsis(self):
        """ Short summary about the bug """
        # Summary in the form: BUG#123456 (BZ#123, TC#456, CR#789)
        return "{0} ({1})".format(
            self.identifier, ", ".join([str(self)] + [
                obj.identifier
                for obj in (self.testcase, self.caserun) if obj is not None
            ]))

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Bug Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, bug=None, system=1, **kwargs):
        """
        Initialize the bug

        Provide external bug id, optionally bug system (Bugzilla by default).
        """

        # Initialize (unless already done)
        id, ignore, inject, initialized = self._is_initialized(id)
        if initialized:
            return
        TCMS.__init__(self, id, prefix="BUG")

        # If inject given, fetch bug data from it
        if inject:
            self._fetch(inject)
        # Initialized by bug id and system id
        elif bug is not None and system is not None:
            self._bug = bug
            self._system = system
        # Otherwise just check that the id was provided
        elif id is None:
            raise TCMSError("Need bug id to initialize the Bug object.")

    def __eq__(self, other):
        """
        Custom bug comparison

        Primarily decided by id. If unknown, compares by bug id & bug system.
        """
        # Decide by internal id
        if self._id is not TCMSNone and other._id is not TCMSNone:
            return self.id == other.id
        # Compare external id and bug system id
        return self.bug == other.bug and self.system == other.system

    def __str__(self):
        """ Bug name for printing """
        try:
            prefix = self._prefixes[self.system]
        except KeyError:
            prefix = "BZ"
        return "{0}#{1}".format(
            prefix,
            str(self.bug).rjust(self._identifier_width, "0"))

    def __hash__(self):
        """ Construct the uniqe hash from bug id and bug system id """
        return _idify([self.system, self.bug])

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Bug Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inject=None):
        """ Fetch bug info from the server """
        TCMS._fetch(self, inject)
        # No direct xmlrpc function for fetching so far
        if inject is None:
            raise NotImplementedError("Direct bug fetching not implemented")
        # Process provided inject
        self._id = int(inject["id"])
        self._bug = int(inject["bug_id"])
        self._system = int(inject["bug_system_id"])
        self._testcase = TestCase(int(inject["case_id"]))
        if inject["case_run_id"] is not None:
            self._caserun = CaseRun(int(inject["case_run_id"]))
        # Index the fetched object into cache
        self._index()
Example #10
0
class TestPlan(Mutable):
    """
    Test plan.

    Provides test plan attributes and 'testruns' and 'testcases'
    properties, the latter as the default iterator.
    """

    _cache = {}
    _identifier_width = 5

    # List of all object attributes (used for init & expiration)
    _attributes = ["author", "children", "name",
                   "owner", "parent", "product", "status", "tags", "testcases",
                   "testruns", "type", "version"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestPlan Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"), doc="Test plan id.")
    author = property(_getter("author"), doc="Test plan author.")
    children = property(_getter("children"), doc="Child test plans.")
    tags = property(_getter("tags"), doc="Attached tags.")
    testcases = property(_getter("testcases"),
                         doc="Container with test cases linked to this plan.")
    testruns = property(_getter("testruns"),
                        doc="Test runs related to this plan.")

    # Read-write properties
    name = property(_getter("name"), _setter("name"),
                    doc="Test plan name.")
    owner = property(_getter("owner"), _setter("owner"),
                     doc="Test plan owner.")
    parent = property(_getter("parent"), _setter("parent"),
                      doc="Parent test plan.")
    product = property(_getter("product"), _setter("product"),
                       doc="Test plan product.")
    type = property(_getter("type"), _setter("type"),
                    doc="Test plan type.")
    status = property(_getter("status"), _setter("status"),
                      doc="Test plan status.")
    version = property(_getter("version"), _setter("version"),
                       doc="Default product version.")

    @property
    def synopsis(self):
        """ One line test plan overview """
        return "{0} - {1} ({2} cases, {3} runs)".format(
               self.identifier,
               self.name, len(self.testcases), len(self.testruns))

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """
        # ID check
        if isinstance(id, int):
            return cls._cache[id], id

        # Check dictionary (only ID so far)
        if isinstance(id, dict):
            return cls._cache[id["plan_id"]], id["plan_id"]

        raise KeyError

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestPlan Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, name=None, product=None, version=None,
                 type=None, **kwargs):
        """
        Initialize a test plan or create a new one.

        Provide id to initialize an existing test plan or name, product,
        version and type to create a new plan. Other parameters are optional.

            document .... Test plan document (default: '')
            parent ...... Parent test plan (object or id, default: None)

        Product, version and type can be provided as id, name or object.
        """

        # Initialize (unless already done)
        id, ignore, inject, initialized = self._is_initialized(id)
        if initialized:
            return
        Mutable.__init__(self, id, prefix="TP")

        # If inject given, fetch test case data from it
        if inject:
            self._fetch(inject)
        # Create a new test plan if name, type, product and version provided
        elif name and type and product and version:
            self._create(name=name, product=product, version=version,
                         type=type, **kwargs)
        # Otherwise just check that the test plan id was provided
        elif not id:
            raise TCMSError(
                "Need either id or name, product, version "
                "and type to initialize the test plan")

    def __iter__(self):
        """ Provide test cases as the default iterator """
        for testcase in self.testcases:
            yield testcase

    def __str__(self):
        """ Test plan id & summary for printing """
        return "{0} - {1}".format(self.identifier, self.name)

    @staticmethod
    def search(**query):
        """ Search for test plans """
        return [TestPlan(hash)
                for hash in TCMS()._server.TestPlan.filter(dict(query))]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestPlan Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _create(self, name, product, version, type, **kwargs):
        """ Create a new test plan """

        hash = {}

        # Name
        if name is None:
            raise TCMSError("Name required for creating new test plan")
        hash["name"] = name

        # Product
        if product is None:
            raise TCMSError("Product required for creating new test plan")
        elif isinstance(product, (int, str)):
            product = Product(product)
        hash["product"] = product.id

        # Version
        if version is None:
            raise TCMSError("Version required for creating new test plan")
        elif isinstance(version, int):
            version = Version(version)
        elif isinstance(version, str):
            version = Version(name=version, product=product)
        hash["default_product_version"] = version.id

        # Type
        if type is None:
            raise TCMSError("Type required for creating new test plan")
        elif isinstance(type, (int, str)):
            type = PlanType(type)
        hash["type"] = type.id

        # Parent
        parent = kwargs.get("parent")
        if parent is not None:
            if isinstance(parent, int):
                parent = TestPlan(parent)
            hash["parent"] = parent.id

        # Document - if not explicitly specified, put empty text
        hash["text"] = kwargs.get("text", " ")

        # Workaround for BZ#725995
        hash["is_active"] = "1"

        # Submit
        log.info("Creating a new test plan")
        log.data(pretty(hash))
        inject = self._server.TestPlan.create(hash)
        log.data(pretty(inject))
        try:
            self._id = inject["plan_id"]
        except TypeError:
            log.debug("Failed to create a new test plan")
            log.data(pretty(hash))
            log.data(pretty(inject))
            raise TCMSError("Failed to create test plan")
        self._fetch(inject)
        log.info("Successfully created {0}".format(self))

    def _fetch(self, inject=None):
        """ Initialize / refresh test plan data.

        Either fetch them from the server or use provided hash.
        """
        TCMS._fetch(self, inject)

        # Fetch the data hash from the server unless provided
        if inject is None:
            log.info("Fetching test plan " + self.identifier)
            try:
                inject = self._server.TestPlan.filter({'pk': self.id})[0]
            except IndexError as error:
                log.debug(error)
                raise TCMSError(
                    "Failed to fetch test plan TP#{0}".format(self.id))
            self._inject = inject
        # Otherwise just initialize the id from inject
        else:
            self._id = inject["plan_id"]
        log.debug("Initializing test plan " + self.identifier)
        log.data(pretty(inject))
        if "plan_id" not in inject:
            log.data(pretty(inject))
            raise TCMSError("Failed to initialize " + self.identifier)

        # Set up attributes
        self._author = User(inject["author_id"])
        if inject["owner_id"] is not None:
            self._owner = User(inject["owner_id"])
        else:
            self._owner = None
        self._name = inject["name"]
        self._product = Product({
            "id": inject["product_id"],
            "name": inject["product"]})
        self._version = Version({
            "id": inject["product_version_id"],
            "value": inject["product_version"],
            "product_id": inject["product_id"]})
        self._type = PlanType(inject["type_id"])
        self._status = PlanStatus(inject["is_active"] in ["True", True])
        if inject["parent_id"] is not None:
            self._parent = TestPlan(inject["parent_id"])
        else:
            self._parent = None

        # Initialize containers
        self._testcases = PlanCases(self)
        self._testruns = PlanRuns(self)
        self._children = ChildPlans(self)
        # If all tags are cached, initialize them directly from the inject
        if "tag" in inject and Tag._is_cached(inject["tag"]):
            self._tags = PlanTags(
                self, inset=[Tag(tag) for tag in inject["tag"]])
        else:
            self._tags = PlanTags(self)

        # Index the fetched object into cache
        self._index()

    def _update(self):
        """ Save test plan data to the server """

        # Prepare the update hash
        hash = {}
        hash["name"] = self.name
        hash["product"] = self.product.id
        hash["type"] = self.type.id
        hash["is_active"] = self.status.id == 1
        if self.parent is not None:
            hash["parent"] = self.parent.id
        hash["default_product_version"] = self.version.id
        if self.owner is not None:
            hash["owner"] = self.owner.id

        log.info("Updating test plan " + self.identifier)
        log.data(pretty(hash))
        self._server.TestPlan.update(self.id, hash)

    def update(self):
        """ Update self and containers, if modified, to the server """

        # Update containers (if initialized)
        if self._tags is not TCMSNone:
            self.tags.update()
        if self._testcases is not TCMSNone:
            self.testcases.update()
        if self._testruns is not TCMSNone:
            self.testruns.update()
        if self._children is not TCMSNone:
            self.children.update()

        # Update self (if modified)
        Mutable.update(self)
Example #11
0
class TestCase(Mutable):
    """
    Test case.

    Provides test case attributes and 'testplans' property as the
    default iterator. Furthermore contains bugs, components and tags
    properties.
    """

    _cache = {}
    _identifier_width = 7

    # List of all object attributes (used for init & expiration)
    _attributes = ["action", "arguments", "author", "automated",
                   "autoproposed", "breakdown", "bugs", "category", "components",
                   "created", "effect", "link", "manual", "notes", "plans",
                   "priority", "script", "setup", "status", "summary",
                   "tags", "tester", "testplans", "time"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestCase Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"),
                  doc="Test case id (read-only).")
    author = property(_getter("author"),
                      doc="Test case author.")
    created = property(_getter("created"),
                       doc="Test case creation date (datetime).")
    tags = property(_getter("tags"),
                    doc="Attached tags.")
    bugs = property(_getter("bugs"),
                    doc="Attached bugs.")
    testplans = property(_getter("testplans"),
                         doc="Test plans linked to this test case.")
    components = property(_getter("components"),
                          doc="Components related to this test case.")
    setup = property(_getter("setup"),
                     doc="Setup steps to prepare the machine for the test case.")
    action = property(_getter("action"),
                      doc="Actions to be performed.")
    effect = property(_getter("effect"),
                      doc="Expected Results to be measured.")
    breakdown = property(_getter("breakdown"),
                         doc="Breakdown steps to return machine to original state.")

    @property
    def synopsis(self):
        """ Short summary about the test case """
        plans = len(self.testplans)
        return "{0} ({1}, {2}, {3}, {4} {5})".format(
            self, self.category, self.priority, self.status,
            plans, "test plan" if plans == 1 else "test plans")

    # Read-write properties
    automated = property(_getter("automated"), _setter("automated"),
                         doc="Automation flag. True if the test case is automated.")
    autoproposed = property(_getter("autoproposed"), _setter("autoproposed"),
                            doc="True if the test case is proposed for automation.")
    arguments = property(_getter("arguments"), _setter("arguments"),
                         doc="Test script arguments (used for automation).")
    category = property(_getter("category"), _setter("category"),
                        doc="Test case category.")
    link = property(_getter("link"), _setter("link"),
                    doc="Test case reference link.")
    manual = property(_getter("manual"), _setter("manual"),
                      doc="Manual flag. True if the test case is manual.")
    notes = property(_getter("notes"), _setter("notes"),
                     doc="Test case notes.")
    priority = property(_getter("priority"), _setter("priority"),
                        doc="Test case priority.")
    requirement = property(_getter("requirement"), _setter("requirement"),
                           doc="Test case requirements.")
    script = property(_getter("script"), _setter("script"),
                      doc="Test script (used for automation).")
    status = property(_getter("status"), _setter("status"),
                      doc="Current test case status.")
    summary = property(_getter("summary"), _setter("summary"),
                       doc="Summary describing the test case.")
    tester = property(_getter("tester"), _setter("tester"),
                      doc="Default tester.")
    time = property(_getter("time"), _setter("time"),
                    doc="Estimated time.")

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """
        # ID check
        if isinstance(id, int):
            return cls._cache[id], id

        # Check dictionary (only ID so far)
        if isinstance(id, dict):
            return cls._cache[id["case_id"]], id["case_id"]

        raise KeyError

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestCase Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, summary=None, category=None, **kwargs):
        """ Initialize a test case or create a new one.

        Initialize an existing test case (if id provided) or create a
        new one (based on provided summary and category. Other optional
        parameters supported are:

            product ........ product (default: category.product)
            status ......... test case status (default: PROPOSED)
            automated ...... automation flag (default: True)
            autoproposed ... proposed for automation (default: False)
            manual ......... manual flag (default: False)
            priority ....... priority object, id or name (default: P3)
            script ......... test path (default: None)
            arguments ...... script arguments (default: None)
            requirement .... requirement (default: None)
            tester ......... user object or login (default: None)
            link ........... reference link (default: None)

        Examples:

            existing = TestCase(1234)
            sanity = Category(name="Sanity", product="Fedora")
            new = TestCase(summary="Test", category=sanity)
            new = TestCase(summary="Test", category="Sanity", product="Fedora")

        Note: When providing category by name specify product as well.
        """

        # Initialize (unless already done)
        id, name, inject, initialized = self._is_initialized(id)
        if initialized:
            return
        Mutable.__init__(self, id, prefix="TC")

        # If inject given, fetch test case data from it
        if inject:
            self._fetch(inject)
        # Create a new test case based on summary and category
        elif summary and category:
            self._create(summary=summary, category=category, **kwargs)
        # Otherwise just check that the test case id was provided
        elif not id:
            raise TCMSError("Need either id or both summary and category "
                            "to initialize the test case")

    def __str__(self):
        """ Test case id & summary for printing """
        return "{0} - {1}".format(self.identifier, self.summary)

    @staticmethod
    def search(**query):
        """ Search for test cases """
        # Special handling for automated & manual attributes
        manual = automated = None
        if "automated" in query:
            automated = query["automated"]
            del query["automated"]
        if "manual" in query:
            manual = query["manual"]
            del query["manual"]
        # Map to appropriate value of 'is_automated' attribute
        if manual is not None or automated is not None:
            if automated is False and manual is False:
                raise TCMSError("Invalid search "
                                "('manual' and 'automated' cannot be both False)")
            elif automated is False:
                query["is_automated"] = 0
            elif manual is False:
                query["is_automated"] = 1
            elif automated is True and manual is True:
                query["is_automated"] = 2
            elif automated is True:
                query["is_automated__in"] = [1, 2]
            elif manual is True:
                query["is_automated__in"] = [0, 2]
        log.debug("Searching for test cases")
        log.data(pretty(query))
        return [TestCase(inject)
                for inject in TCMS()._server.TestCase.filter(dict(query))]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestCase Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _create(self, summary, category, **kwargs):
        """ Create a new test case """

        hash = {}

        # Summary
        hash["summary"] = summary

        # If category provided as text, we need product as well
        product = kwargs.get("product")
        if isinstance(category, str) and not kwargs.get("product"):
            raise TCMSError(
                "Need product when category specified by name")
        # Category & Product
        if isinstance(category, str):
            category = Category(category=category, product=product)
        elif not isinstance(category, Category):
            raise TCMSError("Invalid category '{0}'".format(category))
        hash["category"] = category.id
        hash["product"] = category.product.id

        # Priority
        priority = kwargs.get("priority")
        if priority is None:
            priority = Priority("P3")
        elif not isinstance(priority, Priority):
            priority = Priority(priority)
        hash["priority"] = priority.id

        # User
        tester = kwargs.get("tester")
        if tester:
            if isinstance(tester, str):
                tester = User(login=tester)
            hash["default_tester"] = tester.login

        # Script, arguments, requirement & reference link
        hash["script"] = kwargs.get("script")
        hash["arguments"] = kwargs.get("arguments")
        hash["requirement"] = kwargs.get("requirement")
        hash["extra_link"] = kwargs.get("link")

        # Case Status
        status = kwargs.get("status")
        if status:
            if isinstance(status, str):
                status = CaseStatus(status)
            hash["case_status"] = status.id

        # Manual, automated and autoproposed
        automated = kwargs.get("automated", True)
        autoproposed = kwargs.get("autoproposed", False)
        manual = kwargs.get("manual", False)
        if automated and manual:
            hash["is_automated"] = 2
        elif automated:
            hash["is_automated"] = 1
        else:
            hash["is_automated"] = 0
        hash["is_automated_proposed"] = autoproposed

        # Estimated time
        hash["estimated_time"] = kwargs.get("time", '00:00:00')

        # Notes
        notes = kwargs.get("notes")
        if notes:
            hash["notes"] = notes

        # Submit
        log.info("Creating a new test case")
        log.data(pretty(hash))
        testcasehash = self._server.TestCase.create(hash)
        log.data(pretty(testcasehash))
        try:
            self._id = testcasehash["case_id"]
        except TypeError:
            log.debug("Failed to create a new test case")
            log.data(pretty(hash))
            log.data(pretty(testcasehash))
            raise TCMSError("Failed to create test case")
        self._fetch(testcasehash)
        log.info("Successfully created {0}".format(self))

    def _fetch(self, inject=None):
        """ Initialize / refresh test case data.

        Either fetch them from the server or use provided hash.
        """
        TCMS._fetch(self, inject)

        # Fetch the data hash from the server unless provided
        if inject is None:
            log.info("Fetching test case " + self.identifier)
            try:
                inject = self._server.TestCase.filter({'pk': self.id})[0]
            except IndexError as error:
                log.debug(error)
                raise TCMSError(
                    "Failed to fetch test case TC#{0}".format(self.id))
            self._inject = inject
        else:
            self._id = inject["case_id"]
        log.debug("Initializing test case " + self.identifier)
        log.data(pretty(inject))

        # Set up attributes
        self._arguments = inject["arguments"]
        self._author = User(inject["author_id"])
        self._category = Category(inject["category_id"])
        if isinstance(inject["create_date"], str):
            self._created = datetime.datetime.strptime(
                inject["create_date"], "%Y-%m-%d %H:%M:%S")
        else:
            self._created = inject["create_date"]
        self._link = inject["extra_link"]
        self._notes = inject["notes"]
        self._priority = Priority(inject["priority_id"])
        self._requirement = inject["requirement"]
        self._script = inject["script"]
        self._status = CaseStatus(inject["case_status_id"])
        self._summary = inject["summary"]
        self._time = inject["estimated_time"]
        if inject["default_tester_id"] is not None:
            self._tester = User(inject["default_tester_id"])
        else:
            self._tester = None

        # Handle manual, automated and autoproposed
        self._automated = inject["is_automated"] in [1, '1', 2, '2']
        self._manual = inject["is_automated"] in [0, '0', 2, '2']
        self._autoproposed = inject["is_automated_proposed"]

        # Empty script or arguments to be handled same as None
        if self._script == "":
            self._script = None
        if self._arguments == "":
            self._arguments = None

        # Test case documentation
        for attribute in ["setup", "action", "effect", "breakdown"]:
            if "text" in inject:
                setattr(self, "_" + attribute, inject["text"][attribute])
            else:
                setattr(self, "_" + attribute, None)

        # Initialize containers
        self._bugs = CaseBugs(self)
        self._testplans = CasePlans(self)
        self._components = CaseComponents(self)
        # If all tags are cached, initialize them directly from the inject
        if "tag" in inject and Tag._is_cached(inject["tag"]):
            self._tags = CaseTags(
                self, inset=[Tag(tag) for tag in inject["tag"]])
        else:
            self._tags = CaseTags(self)

        # Index the fetched object into cache
        self._index()

    def _update(self):
        """ Save test case data to server """
        hash = {}

        hash["arguments"] = self.arguments
        hash["case_status"] = self.status.id
        hash["category"] = self.category.id
        hash["estimated_time"] = self.time
        if self.automated and self.manual:
            hash["is_automated"] = 2
        elif self.automated:
            hash["is_automated"] = 1
        else:
            hash["is_automated"] = 0
        hash["is_automated_proposed"] = self.autoproposed
        hash["extra_link"] = self.link
        hash["notes"] = self.notes
        hash["priority"] = self.priority.id
        hash["product"] = self.category.product.id
        hash["requirement"] = self.requirement
        hash["script"] = self.script
        hash["summary"] = self.summary
        if self.tester:
            hash["default_tester"] = self.tester.login

        log.info("Updating test case " + self.identifier)
        log.data(pretty(hash))
        self._server.TestCase.update(self.id, hash)

    def update(self):
        """ Update self and containers, if modified, to the server """

        # Update containers (if initialized)
        if self._bugs is not TCMSNone:
            self.bugs.update()
        if self._tags is not TCMSNone:
            self.tags.update()
        if self._testplans is not TCMSNone:
            self.testplans.update()
        if self._components is not TCMSNone:
            self._components.update()

        # Update self (if modified)
        Mutable.update(self)
Example #12
0
class TestRun(Mutable):
    """
    Test run.

    Provides test run attributes and 'caseruns' property containing all
    relevant case runs (which is also the default iterator).
    """

    _cache = {}
    _identifier_width = 6

    # List of all object attributes (used for init & expiration)
    _attributes = ["build", "caseruns", "finished", "manager",
                   "notes", "product", "started", "status", "summary", "tags",
                   "tester", "testcases", "testplan", "time"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestRun Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"),
                  doc="Test run id.")
    testplan = property(_getter("testplan"),
                        doc="Test plan related to this test run.")
    tags = property(_getter("tags"),
                    doc="Attached tags.")
    started = property(_getter("started"),
                       doc="Timestamp when the test run was started (datetime).")
    finished = property(_getter("finished"),
                        doc="Timestamp when the test run was finished (datetime).")
    caseruns = property(_getter("caseruns"),
                        doc="CaseRun objects related to this test run.")
    testcases = property(_getter("testcases"),
                         doc="""TestCase objects related to this test run\n
                         Supports common container methods add(), remove() and clear()
                         for adding and removing testcases to/from the test run.""")

    # Read-write properties
    build = property(_getter("build"), _setter("build"),
                     doc="Build relevant for this test run.")
    manager = property(_getter("manager"), _setter("manager"),
                       doc="Manager responsible for this test run.")
    notes = property(_getter("notes"), _setter("notes"),
                     doc="Test run notes.")
    status = property(_getter("status"), _setter("status"),
                      doc="Test run status")
    summary = property(_getter("summary"), _setter("summary"),
                       doc="Test run summary.")
    tester = property(_getter("tester"), _setter("tester"),
                      doc="Default tester.")
    time = property(_getter("time"), _setter("time"),
                    doc="Estimated time.")

    @property
    def synopsis(self):
        """ One-line test run overview """
        return "{0} - {1} ({2} cases)".format(
            self.identifier, self.summary, len(self.caseruns))

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """
        # ID check
        if isinstance(id, int):
            return cls._cache[id], id

        # Check dictionary (only ID so far)
        if isinstance(id, dict):
            return cls._cache[id["run_id"]], id["run_id"]

        raise KeyError

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestRun Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, testplan=None, **kwargs):
        """ Initialize a test run or create a new one.

        Initialize an existing test run if id provided, otherwise create
        a new test run based on specified test plan (required). Other
        parameters are optional and have the following defaults:

            build ....... "unspecified"
            product ..... test run product
            version ..... test run product version
            summary ..... <test plan name> on <build>
            notes ....... ""
            manager ..... current user
            tester ...... current user
        """

        # Initialize (unless already done)
        id, name, inject, initialized = self._is_initialized(id)
        if initialized:
            return
        Mutable.__init__(self, id, prefix="TR")

        # If inject given, fetch test case data from it
        if inject:
            self._fetch(inject)
        # Create a new test run based on provided plan
        elif testplan:
            self._create(testplan=testplan, **kwargs)
        # Otherwise just check that the test run id was provided
        elif not id:
            raise TCMSError(
                "Need either id or test plan to initialize the test run")

    def __iter__(self):
        """ Provide test case runs as the default iterator """
        for caserun in self.caseruns:
            yield caserun

    def __str__(self):
        """ Test run id & summary for printing """
        return "{0} - {1}".format(self.identifier, self.summary)

    @staticmethod
    def search(**query):
        """ Search for test runs """
        return [TestRun(hash)
                for hash in TCMS()._server.TestRun.filter(dict(query))]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  TestRun Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _create(self, testplan, product=None, version=None, build=None,
                summary=None, notes=None, manager=None, tester=None, **kwargs):
        """ Create a new test run """

        hash = {}

        # Test plan
        if isinstance(testplan, int):
            testplan = TestPlan(testplan)
        hash["plan"] = testplan.id

        # Product & version
        if product is None:
            product = testplan.product
        elif not isinstance(product, Product):
            product = Product(product)
        hash["product"] = product.id
        if version is None:
            version = testplan.version
        elif isinstance(version, int):
            version = Version(version)
        else:
            version = Version(name=version, product=product)
        hash["product_version"] = version.id

        # Build
        if build is None:
            build = "unspecified"
        if isinstance(build, str):
            build = Build(build=build, product=product)
        hash["build"] = build.id

        # Summary & notes
        if summary is None:
            summary = "{0} on {1}".format(testplan.name, build)
        if notes is None:
            notes = ""
        hash["summary"] = summary
        hash["notes"] = notes

        # Manager & tester (current user by default)
        if not isinstance(manager, User):
            manager = User(manager)
        if not isinstance(tester, User):
            tester = User(tester)
        hash["manager"] = manager.id
        hash["default_tester"] = tester.id

        # Submit to the server and initialize
        log.info("Creating a new test run based on {0}".format(testplan))
        log.data(pretty(hash))
        testrunhash = self._server.TestRun.create(hash)
        log.data(pretty(testrunhash))
        try:
            self._id = testrunhash["run_id"]
        except TypeError:
            log.debug("Failed to create a new test run based on {0}".format(
                      testplan))
            log.data(pretty(hash))
            log.data(pretty(testrunhash))
            raise TCMSError("Failed to create test run")
        self._fetch(testrunhash)
        # Add newly created test run to testplan.testruns container
        if PlanRuns._is_cached(testplan.testruns):
            testplan.testruns._fetch(list(testplan.testruns) + [self])
        log.info("Successfully created {0}".format(self))

    def _fetch(self, inject=None):
        """ Initialize / refresh test run data.

        Either fetch them from the server or use the provided hash.
        """
        TCMS._fetch(self, inject)

        # Fetch the data hash from the server unless provided
        if inject is None:
            log.info("Fetching test run {0}".format(self.identifier))
            try:
                inject = self._server.TestRun.filter({'pk': self.id})[0]
            except IndexError as error:
                log.debug(error)
                raise TCMSError(
                    "Failed to fetch test run TR#{0}".format(self.id))
            self._inject = inject
        else:
            self._id = inject["run_id"]
        log.debug("Initializing test run {0}".format(self.identifier))
        log.data(pretty(inject))

        # Set up attributes
        self._build = Build(inject["build_id"])
        self._manager = User(inject["manager_id"])
        self._notes = inject["notes"]
        self._status = RunStatus(inject["stop_date"])
        self._old_status = self._status
        self._summary = inject["summary"]
        self._tester = User(inject["default_tester_id"])
        self._testplan = TestPlan(inject["plan_id"])
        self._time = inject["estimated_time"]
        try:
            self._started = datetime.datetime.strptime(
                inject["start_date"], "%Y-%m-%d %H:%M:%S")
        except TypeError:
            self._started = None
        try:
            self._finished = datetime.datetime.strptime(
                inject["stop_date"], "%Y-%m-%d %H:%M:%S")
        except TypeError:
            self._finished = None

        # Initialize containers
        self._caseruns = RunCaseRuns(self)
        self._testcases = RunCases(self)
        self._tags = RunTags(self)

        # Index the fetched object into cache
        self._index()

    def _update(self):
        """ Save test run data to the server """

        # Prepare the update hash
        hash = {}
        hash["build"] = self.build.id
        hash["default_tester"] = self.tester.id
        hash["estimated_time"] = self.time
        hash["manager"] = self.manager.id
        hash["notes"] = self.notes
        # This is required until BZ#731982 is fixed
        hash["product"] = self.build.product.id
        hash["summary"] = self.summary

        # Update status field only if its value has changed. This is to avoid
        # updating the 'Finished at' field, which is done automatically by
        # TCMS even when "switching" from 'Finished' to 'Finished'.
        if self._status != self._old_status:
            self._old_status = self._status
            hash["status"] = self.status.id

        log.info("Updating test run " + self.identifier)
        log.data(pretty(hash))
        self._server.TestRun.update(self.id, hash)

    def update(self):
        """ Update self and containers, if modified, to the server """

        # Update containers (if initialized)
        if self._tags is not TCMSNone:
            self.tags.update()
        if self._caseruns is not TCMSNone:
            self._caseruns.update()
        if self._testcases is not TCMSNone:
            self._testcases.update()

        # Update self (if modified)
        Mutable.update(self)
Example #13
0
class CaseRun(Mutable):
    """
    Test case run.

    Provides case run attributes such as status and assignee, including
    the relevant 'testcase' object.
    """

    _identifier_width = 8

    # By default we do not cache CaseRun objects at all
    _expiration = config.NEVER_CACHE
    _cache = {}

    # List of all object attributes (used for init & expiration)
    _attributes = ["assignee", "bugs", "build", "notes", "sortkey", "status",
                   "testcase", "testrun"]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Case Run Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # Read-only properties
    id = property(_getter("id"),
                  doc="Test case run id.")
    testcase = property(_getter("testcase"),
                        doc="Test case object.")
    testrun = property(_getter("testrun"),
                       doc="Test run object.")
    bugs = property(_getter("bugs"),
                    doc="Attached bugs.")

    # Read-write properties
    assignee = property(_getter("assignee"), _setter("assignee"),
                        doc="Test case run assignee object.")
    build = property(_getter("build"), _setter("build"),
                     doc="Test case run build object.")
    notes = property(_getter("notes"), _setter("notes"),
                     doc="Test case run notes (string).")
    sortkey = property(_getter("sortkey"), _setter("sortkey"),
                       doc="Test case sort key (int).")
    status = property(_getter("status"), _setter("status"),
                      doc="Test case run status object.")

    @classmethod
    def _cache_lookup(cls, id, **kwargs):
        """ Look up cached objects, return found instance and search key """
        # ID check
        if isinstance(id, int):
            return cls._cache[id], id

        # Check dictionary (only ID so far)
        if isinstance(id, dict):
            return cls._cache[id["case_run_id"]], id["case_run_id"]

        raise KeyError

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  CaseRun Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __init__(self, id=None, testcase=None, testrun=None, **kwargs):
        """ Initialize a test case run or create a new one.

        Initialize an existing test case run (if id provided) or create
        a new test case run (based on provided test case and test run).
        """

        # Initialize (unless already done)
        id, name, inject, initialized = self._is_initialized(id, **kwargs)
        if initialized:
            return
        Mutable.__init__(self, id, prefix="CR")

        # If inject given, fetch test case run data from it
        if inject:
            self._fetch(inject, **kwargs)
        # Create a new test case run based on case and run
        elif testcase and testrun:
            self._create(testcase=testcase, testrun=testrun, **kwargs)
        # Otherwise just check that the test case run id was provided
        elif not id:
            raise TCMSError("Need either id or testcase and testrun "
                            "to initialize the case run")

    def __str__(self):
        """ Case run id, status & summary for printing """
        return "{0} - {1} - {2}".format(
            self.status.shortname, self.identifier, self.testcase.summary)

    @staticmethod
    def search(**query):
        """ Search for case runs """
        return [CaseRun(inject) for inject in
                TCMS()._server.TestCaseRun.filter(dict(query))]

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  CaseRun Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _create(self, testcase, testrun, **kwargs):
        """ Create a new case run """

        hash = {}

        # TestCase
        if testcase is None:
            raise TCMSError("Case ID required for new case run")
        elif isinstance(testcase, str):
            testcase = TestCase(testcase)
        hash["case"] = testcase.id

        # TestRun
        if testrun is None:
            raise TCMSError("Run ID required for new case run")
        elif isinstance(testrun, str):
            testrun = TestRun(testrun)
        hash["run"] = testrun.id

        # Build is required by XMLRPC
        build = testrun.build
        hash["build"] = build.id

        # Submit
        log.info("Creating new case run")
        log.data(pretty(hash))
        inject = self._server.TestCaseRun.create(hash)
        log.data(pretty(inject))
        try:
            self._id = inject["case_run_id"]
        except TypeError:
            log.debug("Failed to create new case run")
            log.data(pretty(hash))
            log.data(pretty(inject))
            raise TCMSError("Failed to create case run")
        self._fetch(inject)
        log.info("Successfully created {0}".format(self))

        # And finally add to testcases and caseruns containers
        self.testrun.testcases._fetch(
            [self.testcase] + list(self.testrun.testcases))
        self.testrun.caseruns._fetch(
            [self] + list(self.testrun.caseruns))

    def _fetch(self, inject=None, **kwargs):
        """ Initialize / refresh test case run data.

        Either fetch them from the server or use the supplied hashes.
        """
        TCMS._fetch(self, inject)

        # Fetch the data from the server unless inject provided
        if inject is None:
            log.info("Fetching case run {0}".format(self.identifier))
            inject = self._server.TestCaseRun.filter({'pk': self.id})[0]
            self._inject = inject
        else:
            self._id = inject["case_run_id"]
        log.debug("Initializing case run {0}".format(self.identifier))
        log.data(pretty(inject))

        # Set up attributes
        self._assignee = User(inject["assignee_id"])
        self._build = Build(inject["build_id"])
        self._notes = inject["notes"]
        if inject["sortkey"] is not None:
            self._sortkey = int(inject["sortkey"])
        else:
            self._sortkey = None
        self._status = Status(inject["case_run_status_id"])
        self._testrun = TestRun(inject["run_id"])
        # Initialize attached test case (from dict, object or id)
        testcaseinject = kwargs.get("testcaseinject", None)
        if testcaseinject and isinstance(testcaseinject, dict):
            self._testcase = TestCase(testcaseinject)
        elif testcaseinject and isinstance(testcaseinject, TestCase):
            self._testcase = testcaseinject
        else:
            self._testcase = TestCase(inject["case_id"])

        # Initialize containers
        self._bugs = CaseRunBugs(self)

        # Index the fetched object into cache
        self._index()

    def _update(self):
        """ Save test case run data to the server """

        # Prepare the update hash
        hash = {}
        hash["build"] = self.build.id
        hash["assignee"] = self.assignee.id
        hash["case_run_status"] = self.status.id
        hash["notes"] = self.notes
        hash["sortkey"] = self.sortkey
        # Work around BZ#715596
        if self.notes is None:
            hash["notes"] = ""
        log.info("Updating case run " + self.identifier)
        log.data(pretty(hash))
        self._server.TestCaseRun.update(self.id, hash)

    def update(self):
        """ Update self and containers, if modified, to the server """

        # Update containers (if initialized)
        if self._bugs is not TCMSNone:
            self.bugs.update()

        # Update self (if modified)
        Mutable.update(self)
Example #14
0
class Container(Mutable):
    """
    General container class for handling sets of objects.

    Provides the add() and remove() methods for adding and removing
    objects and the internal _add() and _remove() which perform the
    actual update to the server (implemented by respective class).
    """

    # List of all object attributes (used for init & expiration)
    _attributes = ["current", "original"]

    # Class of objects to be contained (defined in each container)
    _class = None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Container Properties
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    id = property(_getter("id"), doc="Related object id.")

    @property
    def _items(self):
        """ Set representation containing the items """
        if self._current is TCMSNone:
            self._fetch()
        # Fetch the whole container if there are uncached items (except when
        # the container is modified otherwise we would lose local changes).
        if not self._modified and not self._class._is_cached(self._current):
            self._fetch()
        return self._current

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Container Special
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def __new__(cls, object, inset=None):
        """ Create new container objects based on the object id """
        return super(Container, cls).__new__(cls, object.id)

    def __init__(self, object, inset=None):
        """ Initialize container for specified object """
        # Initialize (unless already done)
        if self._id is not None:
            # Initialized but not fetched ---> fetch from the inset if given
            if inset is not None and not self._is_cached(self):
                self._fetch(inset)
            return
        Mutable.__init__(self, object.id)
        self._identifier = object.identifier
        self._object = object
        # Initialize directly if initial set provided
        if inset is not None:
            self._fetch(inset)

    def __iter__(self):
        """ Container iterator """
        for item in self._items:
            yield item

    def __getitem__(self, index):
        """ Indexing support """
        if isinstance(index, int):
            return list(self)[index]
        elif isinstance(index, slice):
            return list(self)[index.start:index.stop:index.step]
        else:
            raise IndexError("Invalid index '{0}'".format(index))

    def __contains__(self, item):
        """ Container 'in' operator """
        return item in self._items

    def __len__(self):
        """ Number of container items """
        return len(self._items)

    def __str__(self):
        """ Display items as a list for printing """
        if self._items:
            # List of identifiers
            try:
                return listed(sorted([item.identifier
                                      for item in self._items]))
            # If no identifiers, just join strings
            except AttributeError:
                return listed(self._items, quote="'")
        else:
            return "[None]"

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #  Container Methods
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def _fetch(self, inset=None):
        """ Save cache timestamp and initialize from inset if given """
        TCMS._fetch(self)
        # Create copies of the initial set (if given)
        if inset is not None:
            log.debug("Initializing {0} for {1} from the inset".format(
                self.__class__.__name__, self._identifier))
            log.debug(pretty(inset))
            self._current = set(inset)
            self._original = set(inset)
        # cache into container class
        if config.get_cache_level() >= config.CACHE_OBJECTS:
            self.__class__._cache[self._id] = self
        # Return True if the data are already initialized
        return inset is not None

    def add(self, items):
        """ Add an item or a list of items to the container """

        # Convert to set representation
        if isinstance(items, list):
            items = set(items)
        else:
            items = set([items])

        # If there are any new items
        add_items = items - self._items
        if add_items:
            log.info("Adding {0} to {1}'s {2}".format(
                listed([item.identifier for item in add_items],
                       self._class.__name__,
                       max=10), self._object.identifier,
                self.__class__.__name__))
            self._items.update(items)
            if config.get_cache_level() != config.CACHE_NONE:
                self._modified = True
            else:
                self._update()

    def remove(self, items):
        """ Remove an item or a list of items from the container """

        # Convert to set representation
        if isinstance(items, list):
            items = set(items)
        else:
            items = set([items])

        # If there are any items to be removed
        remove_items = items.intersection(self._items)
        if remove_items:
            log.info("Removing {0} from {1}'s {2}".format(
                listed([item.identifier for item in remove_items],
                       self._class.__name__,
                       max=10), self._object.identifier,
                self.__class__.__name__))
            self._items.difference_update(items)
            if config.get_cache_level() != config.CACHE_NONE:
                self._modified = True
            else:
                self._update()

    def clear(self):
        """ Remove all items from the container """
        self.remove(list(self._items))

    def _add(self, items):
        """ Add provided items to the server """
        raise TCMSError("To be implemented by respective class.")

    def _remove(self, items):
        """ Remove provided items from the server """
        raise TCMSError("To be implemented by respective class.")

    def _update(self):
        """ Update container changes to the server """
        # Added items
        added = self._current - self._original
        if added:
            self._add(added)

        # Removed items
        removed = self._original - self._current
        if removed:
            self._remove(removed)

        # Save the current state as the original (for future updates)
        self._original = set(self._current)

    def _sleep(self):
        """ Prepare container items for caching """
        # When restoring the container from the cache, unpickling failed
        # because of trying to construct set() of objects which were not
        # fully rebuild yet (__hash__ failed because of missing self._id).
        # So we need to convert containers into list of ids before the
        # cache dump and instantiate the objects back after cache restore.
        if self._current is TCMSNone:
            return

        self._original = [item.id for item in self._original]
        self._current = [item.id for item in self._current]

    def _wake(self):
        """ Restore container object after loading from cache """
        # See _sleep() method above for explanation why this is necessary
        if self._current is TCMSNone:
            return

        if self._class._is_cached(list(self._original)):
            log.cache("Waking up {0} for {1}".format(self.__class__.__name__,
                                                     self._identifier))
            self._original = set([self._class(id) for id in self._original])
            self._current = set([self._class(id) for id in self._current])
        else:
            log.cache("Skipping wake up of {0} for {1}".format(
                self.__class__.__name__, self._identifier))
            self._init()