From c8df8f2a502ef77873fceb2d4d63e3949079f59b Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Tue, 14 Jul 2009 13:38:15 +0200 Subject: [PATCH] [entropy.qa] complete entropy.qa docstrings --- libraries/entropy/qa.py | 241 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 220 insertions(+), 21 deletions(-) diff --git a/libraries/entropy/qa.py b/libraries/entropy/qa.py index 2b70b145e..e0db4498e 100644 --- a/libraries/entropy/qa.py +++ b/libraries/entropy/qa.py @@ -1,25 +1,23 @@ # -*- coding: utf-8 -*- -''' - # DESCRIPTION: - # Entropy Object Oriented Interface +""" - Copyright (C) 2007-2009 Fabio Erculiani + @author: Fabio Erculiani + @contact: lxnay@sabayonlinux.org + @copyright: Fabio Erculiani + @license: GPL-2 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. + B{Entropy Framework QA module}. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + This module contains various Quality Assurance routines used by Entropy. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -''' -# pylint ~ok + B{QAInterface} is the main class for QA routines used by Entropy Server + and Entropy Client such as binary packages health check, dependency + test, broken or missing library tes. + + B{ErrorReportInterface} is the HTTP POST based class for Entropy Client + exceptions (errors) submission. + +""" import os import sys import subprocess @@ -34,10 +32,29 @@ from entropy.core import SystemSettings class QAInterface: + """ + Entropy QA interface. This class contains all the Entropy + QA routines used by Entropy Server and Entropy Client. + + An instance of QAInterface can be easily retrieved from + entropy.client.interfaces.Client or entropy.server.interfaces.Server + through an exposed QA() method. + This is anyway a stand-alone class. + + """ + import entropy.tools as entropyTools from entropy.misc import Lifo def __init__(self, OutputInterface): + """ + QAInterface constructor. + @param OutputInterface: class instance used to print output. + Even if not enforced at the moment, it should be a subclass of + entropy.qa.TextInterface exposing the updateProgress() method + with proper signature. + @type OutputInterface: TextInterface class or subclass instance + """ self.Output = OutputInterface self.SystemSettings = SystemSettings() @@ -49,8 +66,29 @@ class QAInterface: raise IncorrectParameter("IncorrectParameter: %s" % (mytxt,)) def test_depends_linking(self, idpackages, dbconn, repo = None): + """ + Scan for broken shared objects linking for the given idpackages on + the given entropy.db.LocalRepository based instance. + Note: this only works for packages actually installed on the running + system. + It is used by Entropy Server during packages injection into database + to warn about potentially broken packages. - repo = self.SystemSettings['repositories']['default_repository'] + @param idpackages: list of valid idpackages (int) on the given dbconn + argument passed + @type idpackages: list + @param dbconn: entropy.db.LocalRepository instance containing the + given idpackages list + @type dbconn: entropy.db.LocalRepository + @keyword repo: repository identifer from which dbconn and idpackages + arguments belong. Note: at the moment it's only used for output + purposes. + @type repo: string + @return: True if any breakage is found, otherwise False + @rtype: bool + """ + if repo is None: + repo = self.SystemSettings['repositories']['default_repository'] scan_msg = blue(_("Now searching for broken depends")) self.Output.updateProgress( @@ -103,7 +141,7 @@ class QAInterface: count = (count, maxcount,) ) mycontent = dbconn.retrieveContent(mydepend) - mybreakages = self.content_test(mycontent) + mybreakages = self._content_test(mycontent) if not mybreakages: continue broken = True @@ -143,8 +181,38 @@ class QAInterface: def scan_missing_dependencies(self, idpackages, dbconn, ask = True, self_check = False, repo = None, black_list = None, black_list_adder = None): + """ + Scan missing dependencies for the given idpackages on the given + entropy.db.LocalRepository "dbconn" instance. In addition, this method + will allow the user through OutputInterface to interactively add (if ask + == True) missing dependencies or blacklist them. - if repo == None: + @param idpackages: list of valid idpackages (int) on the given dbconn + argument passed + @type idpackages: list + @param dbconn: entropy.db.LocalRepository instance containing the + given idpackages list + @type dbconn: entropy.db.LocalRepository + @keyword ask: request user interaction when finding missing dependencies + @type ask: bool + @keyword self_check: also introspect inside the complaining package + (to avoid reporting false positives when circular dependencies + occur) + @type self_check: bool + @keyword repo: repository identifier of the given + entropy.db.LocalRepository dbconn instance. + It is used to correctly place blacklisted items. + @type repo: string + @keyword black_list: list of dependencies already blacklisted. + @type black_list: set + @keyword black_list_adder: callable function that accepts two arguments: + (1) list (set) of new dependencies to blacklist for the + given (2) repository identifier. + @type black_list_adder: callable + @return: tainting status, if any dependency has been added + @rtype: bool + """ + if repo is None: repo = self.SystemSettings['repositories']['default_repository'] if not isinstance(black_list, set): @@ -523,8 +591,18 @@ class QAInterface: return packagesMatched, plain_brokenexecs, 0 - def content_test(self, mycontent): + def _content_test(self, mycontent): + """ + Test whether the given list of files contain files + with broken shared object links. + @param mycontent: list of file paths + @type mycontent: list or set + @return: dict containing a map between file path + and list (set) of broken libraries (just the library name, + the same that is contained inside ELF metadata) + @rtype: dict + """ def is_contained(needed, content): for item in content: if os.path.basename(item) == needed: @@ -559,7 +637,18 @@ class QAInterface: return broken_libs def resolve_dynamic_library(self, library, requiring_executable): + """ + Resolve given library name (as contained into ELF metadata) to + a library path. + @param library: library name (as contained into ELF metadata) + @type library: string + @param requiring_executable: path to ELF object that contains the given + library name + @type requiring_executable: string + @return: resolved library path + @rtype: string + """ def do_resolve(mypaths): found_path = None for mypath in mypaths: @@ -585,7 +674,28 @@ class QAInterface: return found_path def get_missing_rdepends(self, dbconn, idpackage, self_check = False): + """ + Service method able to determine whether dependencies are missing + on the given idpackage (belonging to the given + entropy.db.LocalRepository "dbconn" argument) using shared objects + linking information between packages. + @todo: swap the first two arguments? + @param dbconn: entropy.db.LocalRepository instance from which idpackage + argument belongs + @type dbconn: entropy.db.LocalRepository instance + @param idpackage: entropy.db.LocalRepository package identifier + @type idpackage: int + @keyword self_check: also check inside the given package + (idpackage) itself + @type self_check: bool + @return: tuple of length 2, composed by a dictionary with the + following structure: + {('KEY', 'SLOT': set([list of missing deps for the given key])} + and a "plain" list (set) of missing dependencies + set([list of missing dependencies]) + @rtype: tuple + """ rdepends = {} rdepends_plain = set() neededs = dbconn.retrieveNeeded(idpackage, extended = True) @@ -689,7 +799,24 @@ class QAInterface: return rdepends, rdepends_plain def get_deep_dependency_list(self, dbconn, idpackage, atoms = False): + """ + Service method which returns a complete, expanded list of dependencies + for the given idpackage on the given entropy.db.LocalRepository + "dbconn" instance. + @param dbconn: entropy.db.LocalRepository instance which contains + the given idpackage item. + @type dbconn: entropy.db.LocalRepository instance + @param idpackage: Entropy database package key + @type idpackage: int + @keyword atoms: !! return type modifier !! , make method returning + a list of atom strings instead of list of db match tuples. + @type atoms: bool + @return: list of dependencies in form of matching tuple list + ( [(idpackage, repoid,) ... ] ) or plain dependency list (if + atom == True -- set([atom_string1, atom_string2, atom_string3]) + @rtype: list or set + """ mybuffer = self.Lifo() matchcache = set() depcache = set() @@ -732,7 +859,15 @@ class QAInterface: return matchcache def __analyze_package_edb(self, pkg_path): + """ + Check if the physical Entropy package file contains + a valid Entropy embedded database. + @param pkg_path: path to physical entropy package file + @type pkg_path: string + @return: package validity + @rtype: bool + """ from entropy.db import LocalRepository, dbapi2 fd, tmp_path = tempfile.mkstemp() extract_path = self.entropyTools.extract_edb(pkg_path, tmp_path) @@ -777,6 +912,15 @@ class QAInterface: return valid def entropy_package_checks(self, package_path): + """ + Main method for the execution of QA tests on physical Entropy + package files. + + @param package_path: path to physical Entropy package file path + @type package_path: string + @return: True, if all checks passed + @rtype: bool + """ qa_methods = [self.__analyze_package_edb] for method in qa_methods: qa_rc = method(package_path) @@ -787,8 +931,37 @@ class QAInterface: class ErrorReportInterface: + """ + + Interface used by Entropy Client to remotely send errors via HTTP POST. + Some anonymous info about the running system are collected and sent over, + once the user gives the acknowledgement for this operation. + User should be asked for valid credentials, such as name, surname and email. + This has two advantages: block stupid and lazy people and make possible + for Entropy developers to contact him/her back. + Moreover, the same applies for a simple description. To improve the + ability to debug an issue, it is also asked the user to describe his/her + action prior to the error. + + Sample code: + + >>> from entropy.qa import ErrorReportInterface + >>> error = ErrorReportInterface('http://url_for_http_post') + >>> error.prepare('traceback_text', 'John Foo', 'john@foo.com', + report_data = 'extra traceback info', + description = 'I was installing foo!') + >>> error.submit() + + """ + import entropy.tools as entropyTools def __init__(self, post_url): + """ + ErrorReportInterface constructor. + + @param post_url: HTTP post url where to submit data + @type post_url: string + """ from entropy.misc import MultipartPostHandler import urllib2 self.url = post_url @@ -813,6 +986,25 @@ class ErrorReportInterface: def prepare(self, tb_text, name, email, report_data = "", description = ""): + """ + This method must be called prior to submit(). It is used to prepare + and collect system information before the submission. + It is intentionally split from submit() to allow easy reimplementation. + + @param tb_text: Python traceback text to send + @type tb_text: string + @param name: submitter name + @type name: string + @param email: submitter email address + @type email: string + @keyword report_data: extra information + @type report_data: string + @keyword description: submitter action description + @type description: string + @return: None + @rtype: None + """ + import sys from entropy.tools import getstatusoutput self.params['arch'] = etpConst['currentarch'] @@ -839,6 +1031,13 @@ class ErrorReportInterface: # params is a dict, key(HTTP post item name): value def submit(self): + """ + Submit collected data remotely via HTTP POST. + + @raise PermissionDenied: when prepare() hasn't been called. + @return: None + @rtype: None + """ if self.generated: result = self.opener.open(self.url, self.params).read() if result.strip() == "1":