Back to index

apport  2.4
Public Member Functions | Public Attributes | Private Member Functions
apport.crashdb_impl.memory.CrashDatabase Class Reference
Inheritance diagram for apport.crashdb_impl.memory.CrashDatabase:
Inheritance graph
[legend]
Collaboration diagram for apport.crashdb_impl.memory.CrashDatabase:
Collaboration graph
[legend]

List of all members.

Public Member Functions

def __init__
def upload
def get_comment_url
def get_id_url
def download
def get_affected_packages
def is_reporter
def can_update
def update
def get_distro_release
def get_unfixed
def get_fixed_version
def duplicate_of
def close_duplicate
def mark_regression
def mark_retraced
def get_unretraced
def get_dup_unchecked
def latest_id
def add_dummy_data
def get_bugpattern_baseurl
def accepts
def init_duplicate_db
def check_duplicate
def known
def duplicate_db_fixed
def duplicate_db_remove
def duplicate_db_change_master_id
def duplicate_db_publish
def duplicate_sig_hash
def update_traces
def set_credentials
def mark_retrace_failed

Public Attributes

 reports
 unretraced
 dup_unchecked
 auth_file
 options
 duplicate_db
 format_version

Private Member Functions

def _mark_dup_checked

Detailed Description

Simple implementation of crash database interface which keeps everything
in memory.

This is mainly useful for testing and debugging.

Definition at line 16 of file memory.py.


Constructor & Destructor Documentation

def apport.crashdb_impl.memory.CrashDatabase.__init__ (   self,
  auth_file,
  options 
)
Initialize crash database connection.

This class does not support bug patterns and authentication.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 22 of file memory.py.

00022 
00023     def __init__(self, auth_file, options):
00024         '''Initialize crash database connection.
00025 
00026         This class does not support bug patterns and authentication.'''
00027 
00028         apport.crashdb.CrashDatabase.__init__(self, auth_file, options)
00029 
00030         self.reports = []  # list of dictionaries with keys: report, fixed_version, dup_of, comment
00031         self.unretraced = set()
00032         self.dup_unchecked = set()
00033 
00034         if 'dummy_data' in options:
00035             self.add_dummy_data()

Here is the call graph for this function:


Member Function Documentation

def apport.crashdb_impl.memory.CrashDatabase._mark_dup_checked (   self,
  id,
  report 
) [private]
Mark crash id as checked for being a duplicate.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 185 of file memory.py.

00185 
00186     def _mark_dup_checked(self, id, report):
00187         '''Mark crash id as checked for being a duplicate.'''
00188 
00189         try:
00190             self.dup_unchecked.remove(id)
00191         except KeyError:
00192             pass  # happens when trying to check for dup twice

def apport.crashdb.CrashDatabase.accepts (   self,
  report 
) [inherited]
Check if this report can be uploaded to this database.

Crash databases might limit the types of reports they get with e. g.
the "problem_types" option.

Definition at line 60 of file crashdb.py.

00060 
00061     def accepts(self, report):
00062         '''Check if this report can be uploaded to this database.
00063 
00064         Crash databases might limit the types of reports they get with e. g.
00065         the "problem_types" option.
00066         '''
00067         if 'problem_types' in self.options:
00068             return report.get('ProblemType') in self.options['problem_types']
00069 
00070         return True

Here is the caller graph for this function:

Add some dummy crash reports.

This is mostly useful for test suites.

Definition at line 219 of file memory.py.

00219 
00220     def add_dummy_data(self):
00221         '''Add some dummy crash reports.
00222 
00223         This is mostly useful for test suites.'''
00224 
00225         # signal crash with source package and complete stack trace
00226         r = apport.Report()
00227         r['Package'] = 'libfoo1 1.2-3'
00228         r['SourcePackage'] = 'foo'
00229         r['DistroRelease'] = 'FooLinux Pi/2'
00230         r['Signal'] = '11'
00231         r['ExecutablePath'] = '/bin/crash'
00232 
00233         r['StacktraceTop'] = '''foo_bar (x=1) at crash.c:28
00234 d01 (x=1) at crash.c:29
00235 raise () from /lib/libpthread.so.0
00236 <signal handler called>
00237 __frob (x=1) at crash.c:30'''
00238         self.upload(r)
00239 
00240         # duplicate of above crash (slightly different arguments and
00241         # package version)
00242         r = apport.Report()
00243         r['Package'] = 'libfoo1 1.2-4'
00244         r['SourcePackage'] = 'foo'
00245         r['DistroRelease'] = 'Testux 1.0'
00246         r['Signal'] = '11'
00247         r['ExecutablePath'] = '/bin/crash'
00248 
00249         r['StacktraceTop'] = '''foo_bar (x=2) at crash.c:28
00250 d01 (x=3) at crash.c:29
00251 raise () from /lib/libpthread.so.0
00252 <signal handler called>
00253 __frob (x=4) at crash.c:30'''
00254         self.upload(r)
00255 
00256         # unrelated signal crash
00257         r = apport.Report()
00258         r['Package'] = 'bar 42-4'
00259         r['SourcePackage'] = 'bar'
00260         r['DistroRelease'] = 'Testux 1.0'
00261         r['Signal'] = '11'
00262         r['ExecutablePath'] = '/usr/bin/broken'
00263 
00264         r['StacktraceTop'] = '''h (p=0x0) at crash.c:25
00265 g (x=1, y=42) at crash.c:26
00266 f (x=1) at crash.c:27
00267 e (x=1) at crash.c:28
00268 d (x=1) at crash.c:29'''
00269         self.upload(r)
00270 
00271         # Python crash
00272         r = apport.Report()
00273         r['Package'] = 'python-goo 3epsilon1'
00274         r['SourcePackage'] = 'pygoo'
00275         r['DistroRelease'] = 'Testux 2.2'
00276         r['ExecutablePath'] = '/usr/bin/pygoo'
00277         r['Traceback'] = '''Traceback (most recent call last):
00278   File "test.py", line 7, in <module>
00279     print(_f(5))
00280   File "test.py", line 5, in _f
00281     return g_foo00(x+1)
00282   File "test.py", line 2, in g_foo00
00283     return x/0
00284 ZeroDivisionError: integer division or modulo by zero'''
00285         self.upload(r)
00286 
00287         # Python crash reoccurs in a later version (used for regression detection)
00288         r = apport.Report()
00289         r['Package'] = 'python-goo 5'
00290         r['SourcePackage'] = 'pygoo'
00291         r['DistroRelease'] = 'Testux 2.2'
00292         r['ExecutablePath'] = '/usr/bin/pygoo'
00293         r['Traceback'] = '''Traceback (most recent call last):
00294   File "test.py", line 7, in <module>
00295     print(_f(5))
00296   File "test.py", line 5, in _f
00297     return g_foo00(x+1)
00298   File "test.py", line 2, in g_foo00
00299     return x/0
00300 ZeroDivisionError: integer division or modulo by zero'''
00301         self.upload(r)

Here is the call graph for this function:

Check whether the user is eligible to update a report.

A user should add additional information to an existing ID if (s)he is
the reporter or subscribed, the bug is open, not a duplicate, etc. The
exact policy and checks should be done according to  the particular
implementation.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 86 of file memory.py.

00086 
00087     def can_update(self, id):
00088         '''Check whether the user is eligible to update a report.
00089 
00090         A user should add additional information to an existing ID if (s)he is
00091         the reporter or subscribed, the bug is open, not a duplicate, etc. The
00092         exact policy and checks should be done according to  the particular
00093         implementation.
00094         '''
00095         return self.is_reporter(id)

Here is the call graph for this function:

def apport.crashdb.CrashDatabase.check_duplicate (   self,
  id,
  report = None 
) [inherited]
Check whether a crash is already known.

If the crash is new, it will be added to the duplicate database and the
function returns None. If the crash is already known, the function
returns a pair (crash_id, fixed_version), where fixed_version might be
None if the crash is not fixed in the latest version yet. Depending on
whether the version in report is smaller than/equal to the fixed
version or larger, this calls close_duplicate() or mark_regression().

If the report does not have a valid crash signature, this function does
nothing and just returns None.

By default, the report gets download()ed, but for performance reasons
it can be explicitly passed to this function if it is already available.

Definition at line 133 of file crashdb.py.

00133 
00134     def check_duplicate(self, id, report=None):
00135         '''Check whether a crash is already known.
00136 
00137         If the crash is new, it will be added to the duplicate database and the
00138         function returns None. If the crash is already known, the function
00139         returns a pair (crash_id, fixed_version), where fixed_version might be
00140         None if the crash is not fixed in the latest version yet. Depending on
00141         whether the version in report is smaller than/equal to the fixed
00142         version or larger, this calls close_duplicate() or mark_regression().
00143 
00144         If the report does not have a valid crash signature, this function does
00145         nothing and just returns None.
00146 
00147         By default, the report gets download()ed, but for performance reasons
00148         it can be explicitly passed to this function if it is already available.
00149         '''
00150         assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
00151 
00152         if not report:
00153             report = self.download(id)
00154 
00155         self._mark_dup_checked(id, report)
00156 
00157         if 'DuplicateSignature' in report:
00158             sig = report['DuplicateSignature']
00159         else:
00160             sig = report.crash_signature()
00161         existing = []
00162         if sig:
00163             # use real duplicate signature
00164             existing = self._duplicate_search_signature(sig, id)
00165 
00166             if existing:
00167                 # update status of existing master bugs
00168                 for (ex_id, _) in existing:
00169                     self._duplicate_db_sync_status(ex_id)
00170                 existing = self._duplicate_search_signature(sig, id)
00171 
00172         try:
00173             report_package_version = report['Package'].split()[1]
00174         except (KeyError, IndexError):
00175             report_package_version = None
00176 
00177         # check the existing IDs whether there is one that is unfixed or not
00178         # older than the report's package version; if so, we have a duplicate.
00179         master_id = None
00180         master_ver = None
00181         for (ex_id, ex_ver) in existing:
00182             if not ex_ver or not report_package_version or apport.packaging.compare_versions(report_package_version, ex_ver) < 0:
00183                 master_id = ex_id
00184                 master_ver = ex_ver
00185                 break
00186         else:
00187             # if we did not find a new enough open master report,
00188             # we have a regression of the latest fix. Mark it so, and create a
00189             # new unfixed ID for it later on
00190             if existing:
00191                 self.mark_regression(id, existing[-1][0])
00192 
00193         # now query address signatures, they might turn up another duplicate
00194         # (not necessarily the same, due to Stacktraces sometimes being
00195         # slightly different)
00196         addr_sig = report.crash_signature_addresses()
00197         if addr_sig:
00198             addr_match = self._duplicate_search_address_signature(addr_sig)
00199             if addr_match and addr_match != master_id:
00200                 if master_id is None:
00201                     # we have a duplicate only identified by address sig, close it
00202                     master_id = addr_match
00203                 else:
00204                     # our bug is a dupe of two different masters, one from
00205                     # symbolic, the other from addr matching (see LP#943117);
00206                     # make them all duplicates of each other, using the lower
00207                     # number as master
00208                     if master_id < addr_match:
00209                         self.close_duplicate(report, addr_match, master_id)
00210                         self._duplicate_db_merge_id(addr_match, master_id)
00211                     else:
00212                         self.close_duplicate(report, master_id, addr_match)
00213                         self._duplicate_db_merge_id(master_id, addr_match)
00214                         master_id = addr_match
00215                         master_ver = None  # no version tracking for address signatures yet
00216 
00217         if master_id is not None and master_id != id:
00218             if addr_sig:
00219                 self._duplicate_db_add_address_signature(addr_sig, master_id)
00220             self.close_duplicate(report, id, master_id)
00221             return (master_id, master_ver)
00222 
00223         # no duplicate detected; create a new record for the ID if we don't have one already
00224         if sig:
00225             cur = self.duplicate_db.cursor()
00226             cur.execute('SELECT count(*) FROM crashes WHERE crash_id == ?', [id])
00227             count_id = cur.fetchone()[0]
00228             if count_id == 0:
00229                 cur.execute('INSERT INTO crashes VALUES (?, ?, ?, CURRENT_TIMESTAMP)', (_u(sig), id, None))
00230                 self.duplicate_db.commit()
00231         if addr_sig:
00232             self._duplicate_db_add_address_signature(addr_sig, id)
00233 
00234         return None

Here is the call graph for this function:

def apport.crashdb_impl.memory.CrashDatabase.close_duplicate (   self,
  report,
  id,
  master 
)
Mark a crash id as duplicate of given master ID.

If master is None, id gets un-duplicated.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 171 of file memory.py.

00171 
00172     def close_duplicate(self, report, id, master):
00173         '''Mark a crash id as duplicate of given master ID.
00174 
00175         If master is None, id gets un-duplicated.
00176         '''
00177         self.reports[id]['dup_of'] = master

Download the problem report from given ID and return a Report.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 71 of file memory.py.

00071 
00072     def download(self, id):
00073         '''Download the problem report from given ID and return a Report.'''
00074 
00075         return self.reports[id]['report']

def apport.crashdb.CrashDatabase.duplicate_db_change_master_id (   self,
  old_id,
  new_id 
) [inherited]
Change a crash ID.

Definition at line 332 of file crashdb.py.

00332 
00333     def duplicate_db_change_master_id(self, old_id, new_id):
00334         '''Change a crash ID.'''
00335 
00336         assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
00337 
00338         cur = self.duplicate_db.cursor()
00339         cur.execute('UPDATE crashes SET crash_id = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?',
00340                     [new_id, old_id])
00341         cur.execute('UPDATE address_signatures SET crash_id = ? WHERE crash_id = ?',
00342                     [new_id, old_id])
00343         self.duplicate_db.commit()

def apport.crashdb.CrashDatabase.duplicate_db_fixed (   self,
  id,
  version 
) [inherited]
Mark given crash ID as fixed in the duplicate database.

version specifies the package version the crash was fixed in (None for
'still unfixed').

Definition at line 306 of file crashdb.py.

00306 
00307     def duplicate_db_fixed(self, id, version):
00308         '''Mark given crash ID as fixed in the duplicate database.
00309 
00310         version specifies the package version the crash was fixed in (None for
00311         'still unfixed').
00312         '''
00313         assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
00314 
00315         cur = self.duplicate_db.cursor()
00316         n = cur.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?',
00317                         (version, id))
00318         assert n.rowcount == 1
00319         self.duplicate_db.commit()

Here is the caller graph for this function:

def apport.crashdb.CrashDatabase.duplicate_db_publish (   self,
  dir 
) [inherited]
Create text files suitable for www publishing.

Create a number of text files in the given directory which Apport
clients can use to determine whether a problem is already reported to
the database, through the known() method. This directory is suitable
for publishing to the web.

The database is indexed by the first two fields of the duplicate or
crash signature, to avoid having to download the entire database every
time.

If the directory already exists, it will be updated. The new content is
built in a new directory which is the given one with ".new" appended,
then moved to the given name in an almost atomic way.

Definition at line 344 of file crashdb.py.

00344 
00345     def duplicate_db_publish(self, dir):
00346         '''Create text files suitable for www publishing.
00347 
00348         Create a number of text files in the given directory which Apport
00349         clients can use to determine whether a problem is already reported to
00350         the database, through the known() method. This directory is suitable
00351         for publishing to the web.
00352 
00353         The database is indexed by the first two fields of the duplicate or
00354         crash signature, to avoid having to download the entire database every
00355         time.
00356 
00357         If the directory already exists, it will be updated. The new content is
00358         built in a new directory which is the given one with ".new" appended,
00359         then moved to the given name in an almost atomic way.
00360         '''
00361         assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
00362 
00363         # first create the temporary new dir; if that fails, nothing has been
00364         # changed and we fail early
00365         out = dir + '.new'
00366         os.mkdir(out)
00367 
00368         # crash addresses
00369         addr_base = os.path.join(out, 'address')
00370         os.mkdir(addr_base)
00371         cur_hash = None
00372         cur_file = None
00373 
00374         cur = self.duplicate_db.cursor()
00375 
00376         cur.execute('SELECT * from address_signatures ORDER BY signature')
00377         for (sig, id) in cur.fetchall():
00378             h = self.duplicate_sig_hash(sig)
00379             if h is None:
00380                 # some entries can't be represented in a single line
00381                 continue
00382             if h != cur_hash:
00383                 cur_hash = h
00384                 if cur_file:
00385                     cur_file.close()
00386                 cur_file = open(os.path.join(addr_base, cur_hash), 'w')
00387 
00388             cur_file.write('%i %s\n' % (id, sig))
00389 
00390         if cur_file:
00391             cur_file.close()
00392 
00393         # duplicate signatures
00394         sig_base = os.path.join(out, 'sig')
00395         os.mkdir(sig_base)
00396         cur_hash = None
00397         cur_file = None
00398 
00399         cur.execute('SELECT signature, crash_id from crashes ORDER BY signature')
00400         for (sig, id) in cur.fetchall():
00401             h = self.duplicate_sig_hash(sig)
00402             if h is None:
00403                 # some entries can't be represented in a single line
00404                 continue
00405             if h != cur_hash:
00406                 cur_hash = h
00407                 if cur_file:
00408                     cur_file.close()
00409                 cur_file = open(os.path.join(sig_base, cur_hash), 'wb')
00410 
00411             cur_file.write(('%i %s\n' % (id, sig)).encode('UTF-8'))
00412 
00413         if cur_file:
00414             cur_file.close()
00415 
00416         # switch over tree; this is as atomic as we can be with directories
00417         if os.path.exists(dir):
00418             os.rename(dir, dir + '.old')
00419         os.rename(out, dir)
00420         if os.path.exists(dir + '.old'):
00421             shutil.rmtree(dir + '.old')

Here is the call graph for this function:

def apport.crashdb.CrashDatabase.duplicate_db_remove (   self,
  id 
) [inherited]
Remove crash from the duplicate database.

This happens when a report got rejected or manually duplicated.

Definition at line 320 of file crashdb.py.

00320 
00321     def duplicate_db_remove(self, id):
00322         '''Remove crash from the duplicate database.
00323 
00324         This happens when a report got rejected or manually duplicated.
00325         '''
00326         assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
00327 
00328         cur = self.duplicate_db.cursor()
00329         cur.execute('DELETE FROM crashes WHERE crash_id = ?', [id])
00330         cur.execute('DELETE FROM address_signatures WHERE crash_id = ?', [id])
00331         self.duplicate_db.commit()

Here is the caller graph for this function:

Return master ID for a duplicate bug.

If the bug is not a duplicate, return None.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 164 of file memory.py.

00164 
00165     def duplicate_of(self, id):
00166         '''Return master ID for a duplicate bug.
00167 
00168         If the bug is not a duplicate, return None.
00169         '''
00170         return self.reports[id]['dup_of']

def apport.crashdb.CrashDatabase.duplicate_sig_hash (   klass,
  sig 
) [inherited]
Create a www/URL proof hash for a duplicate signature

Definition at line 586 of file crashdb.py.

00586 
00587     def duplicate_sig_hash(klass, sig):
00588         '''Create a www/URL proof hash for a duplicate signature'''
00589 
00590         # cannot hash multi-line custom duplicate signatures
00591         if '\n' in sig:
00592             return None
00593 
00594         # custom DuplicateSignatures have a free format, split off first word
00595         i = sig.split(' ', 1)[0]
00596         # standard crash/address signatures use ':' as field separator, usually
00597         # for ExecutableName:Signal
00598         i = '_'.join(i.split(':', 2)[:2])
00599         # we manually quote '/' to make them nicer to read
00600         i = i.replace('/', '_')
00601         i = quote_plus(i.encode('UTF-8'))
00602         # avoid too long file names
00603         i = i[:200]
00604         return i

Here is the caller graph for this function:

Return list of affected source packages for given ID.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 76 of file memory.py.

00076 
00077     def get_affected_packages(self, id):
00078         '''Return list of affected source packages for given ID.'''
00079 
00080         return [self.reports[id]['report']['SourcePackage']]

Return the base URL for bug patterns.

See apport.report.Report.search_bug_patterns() for details. If this
function returns None, bug patterns are disabled.

Definition at line 52 of file crashdb.py.

00052 
00053     def get_bugpattern_baseurl(self):
00054         '''Return the base URL for bug patterns.
00055 
00056         See apport.report.Report.search_bug_patterns() for details. If this
00057         function returns None, bug patterns are disabled.
00058         '''
00059         return self.options.get('bug_pattern_url')

def apport.crashdb_impl.memory.CrashDatabase.get_comment_url (   self,
  report,
  handle 
)
Return http://<sourcepackage>.bugs.example.com/<handle> for package bugs
or http://bugs.example.com/<handle> for reports without a SourcePackage.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 52 of file memory.py.

00052 
00053     def get_comment_url(self, report, handle):
00054         '''Return http://<sourcepackage>.bugs.example.com/<handle> for package bugs
00055         or http://bugs.example.com/<handle> for reports without a SourcePackage.'''
00056 
00057         if 'SourcePackage' in report:
00058             return 'http://%s.bugs.example.com/%i' % (report['SourcePackage'], handle)
00059         else:
00060             return 'http://bugs.example.com/%i' % handle

Get 'DistroRelease: <release>' from the given report ID and return
it.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 123 of file memory.py.

00123 
00124     def get_distro_release(self, id):
00125         '''Get 'DistroRelease: <release>' from the given report ID and return
00126         it.'''
00127 
00128         return self.reports[id]['report']['DistroRelease']

Return an ID set of all crashes which have not been checked for
being a duplicate.

This is mainly useful for crashes of scripting languages such as
Python, since they do not need to be retraced. It should not return
bugs that are covered by get_unretraced().

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 204 of file memory.py.

00204 
00205     def get_dup_unchecked(self):
00206         '''Return an ID set of all crashes which have not been checked for
00207         being a duplicate.
00208 
00209         This is mainly useful for crashes of scripting languages such as
00210         Python, since they do not need to be retraced. It should not return
00211         bugs that are covered by get_unretraced().'''
00212 
00213         return self.dup_unchecked

Return the package version that fixes a given crash.

Return None if the crash is not yet fixed, or an empty string if the
crash is fixed, but it cannot be determined by which version. Return
'invalid' if the crash report got invalidated, such as closed a
duplicate or rejected.

This function should make sure that the returned result is correct. If
there are any errors with connecting to the crash database, it should
raise an exception (preferably IOError).

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 145 of file memory.py.

00145 
00146     def get_fixed_version(self, id):
00147         '''Return the package version that fixes a given crash.
00148 
00149         Return None if the crash is not yet fixed, or an empty string if the
00150         crash is fixed, but it cannot be determined by which version. Return
00151         'invalid' if the crash report got invalidated, such as closed a
00152         duplicate or rejected.
00153 
00154         This function should make sure that the returned result is correct. If
00155         there are any errors with connecting to the crash database, it should
00156         raise an exception (preferably IOError).'''
00157 
00158         try:
00159             if self.reports[id]['dup_of'] is not None:
00160                 return 'invalid'
00161             return self.reports[id]['fixed_version']
00162         except IndexError:
00163             return 'invalid'

def apport.crashdb_impl.memory.CrashDatabase.get_id_url (   self,
  report,
  id 
)
Return URL for a given report ID.

The report is passed in case building the URL needs additional
information from it, such as the SourcePackage name.

Return None if URL is not available or cannot be determined.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 61 of file memory.py.

00061 
00062     def get_id_url(self, report, id):
00063         '''Return URL for a given report ID.
00064 
00065         The report is passed in case building the URL needs additional
00066         information from it, such as the SourcePackage name.
00067 
00068         Return None if URL is not available or cannot be determined.
00069         '''
00070         return self.get_comment_url(report, id)

Here is the call graph for this function:

Return an ID set of all crashes which are not yet fixed.

The list must not contain bugs which were rejected or duplicate.

This function should make sure that the returned list is correct. If
there are any errors with connecting to the crash database, it should
raise an exception (preferably IOError).

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 129 of file memory.py.

00129 
00130     def get_unfixed(self):
00131         '''Return an ID set of all crashes which are not yet fixed.
00132 
00133         The list must not contain bugs which were rejected or duplicate.
00134 
00135         This function should make sure that the returned list is correct. If
00136         there are any errors with connecting to the crash database, it should
00137         raise an exception (preferably IOError).'''
00138 
00139         result = set()
00140         for i in range(len(self.reports)):
00141             if self.reports[i]['dup_of'] is None and self.reports[i]['fixed_version'] is None:
00142                 result.add(i)
00143 
00144         return result

Return an ID set of all crashes which have not been retraced yet and
which happened on the current host architecture.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 198 of file memory.py.

00198 
00199     def get_unretraced(self):
00200         '''Return an ID set of all crashes which have not been retraced yet and
00201         which happened on the current host architecture.'''
00202 
00203         return self.unretraced

def apport.crashdb.CrashDatabase.init_duplicate_db (   self,
  path 
) [inherited]
Initialize duplicate database.

path specifies an SQLite database. It will be created if it does not
exist yet.

Definition at line 76 of file crashdb.py.

00076 
00077     def init_duplicate_db(self, path):
00078         '''Initialize duplicate database.
00079 
00080         path specifies an SQLite database. It will be created if it does not
00081         exist yet.
00082         '''
00083         import sqlite3 as dbapi2
00084 
00085         assert dbapi2.paramstyle == 'qmark', \
00086             'this module assumes qmark dbapi parameter style'
00087 
00088         self.format_version = 3
00089 
00090         init = not os.path.exists(path) or path == ':memory:' or \
00091             os.path.getsize(path) == 0
00092         self.duplicate_db = dbapi2.connect(path, timeout=7200)
00093 
00094         if init:
00095             cur = self.duplicate_db.cursor()
00096             cur.execute('CREATE TABLE version (format INTEGER NOT NULL)')
00097             cur.execute('INSERT INTO version VALUES (?)', [self.format_version])
00098 
00099             cur.execute('''CREATE TABLE crashes (
00100                 signature VARCHAR(255) NOT NULL,
00101                 crash_id INTEGER NOT NULL,
00102                 fixed_version VARCHAR(50),
00103                 last_change TIMESTAMP,
00104                 CONSTRAINT crashes_pk PRIMARY KEY (crash_id))''')
00105 
00106             cur.execute('''CREATE TABLE address_signatures (
00107                 signature VARCHAR(1000) NOT NULL,
00108                 crash_id INTEGER NOT NULL,
00109                 CONSTRAINT address_signatures_pk PRIMARY KEY (signature))''')
00110 
00111             self.duplicate_db.commit()
00112 
00113         # verify integrity
00114         cur = self.duplicate_db.cursor()
00115         cur.execute('PRAGMA integrity_check')
00116         result = cur.fetchall()
00117         if result != [('ok',)]:
00118             raise SystemError('Corrupt duplicate db:' + str(result))
00119 
00120         try:
00121             cur.execute('SELECT format FROM version')
00122             result = cur.fetchone()
00123         except self.duplicate_db.OperationalError as e:
00124             if 'no such table' in str(e):
00125                 # first db format did not have version table yet
00126                 result = [0]
00127         if result[0] > self.format_version:
00128             raise SystemError('duplicate DB has unknown format %i' % result[0])
00129         if result[0] < self.format_version:
00130             print('duplicate db has format %i, upgrading to %i' %
00131                   (result[0], self.format_version))
00132             self._duplicate_db_upgrade(result[0])

Check whether the user is the reporter of given ID.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 81 of file memory.py.

00081 
00082     def is_reporter(self, id):
00083         '''Check whether the user is the reporter of given ID.'''
00084 
00085         return True

def apport.crashdb.CrashDatabase.known (   self,
  report 
) [inherited]
Check if the crash db already knows about the crash signature.

Check if the report has a DuplicateSignature, crash_signature(), or
StacktraceAddressSignature, and ask the database whether the problem is
already known. If so, return an URL where the user can check the status
or subscribe (if available), or just return True if the report is known
but there is no public URL. In that case the report will not be
uploaded (i. e. upload() will not be called).

Return None if the report does not have any signature or the crash
database does not support checking for duplicates on the client side.

The default implementation uses a text file format generated by
duplicate_db_publish() at an URL specified by the "dupdb_url" option.
Subclasses are free to override this with a custom implementation, such
as a real database lookup.

Reimplemented in apport.crashdb_impl.launchpad.CrashDatabase.

Definition at line 235 of file crashdb.py.

00235 
00236     def known(self, report):
00237         '''Check if the crash db already knows about the crash signature.
00238 
00239         Check if the report has a DuplicateSignature, crash_signature(), or
00240         StacktraceAddressSignature, and ask the database whether the problem is
00241         already known. If so, return an URL where the user can check the status
00242         or subscribe (if available), or just return True if the report is known
00243         but there is no public URL. In that case the report will not be
00244         uploaded (i. e. upload() will not be called).
00245 
00246         Return None if the report does not have any signature or the crash
00247         database does not support checking for duplicates on the client side.
00248 
00249         The default implementation uses a text file format generated by
00250         duplicate_db_publish() at an URL specified by the "dupdb_url" option.
00251         Subclasses are free to override this with a custom implementation, such
00252         as a real database lookup.
00253         '''
00254         if not self.options.get('dupdb_url'):
00255             return None
00256 
00257         for kind in ('sig', 'address'):
00258             # get signature
00259             if kind == 'sig':
00260                 if 'DuplicateSignature' in report:
00261                     sig = report['DuplicateSignature']
00262                 else:
00263                     sig = report.crash_signature()
00264             else:
00265                 sig = report.crash_signature_addresses()
00266 
00267             if not sig:
00268                 continue
00269 
00270             # build URL where the data should be
00271             h = self.duplicate_sig_hash(sig)
00272             if not h:
00273                 return None
00274 
00275             # the hash is already quoted, but we really want to open the quoted
00276             # file names; as urlopen() unquotes, we need to double-quote here
00277             # again so that urlopen() sees the single-quoted file names
00278             url = os.path.join(self.options['dupdb_url'], kind, quote_plus(h))
00279 
00280             # read data file
00281             try:
00282                 f = urlopen(url)
00283                 contents = f.read().decode('UTF-8')
00284                 f.close()
00285                 if '<title>404 Not Found' in contents:
00286                     continue
00287             except (IOError, URLError):
00288                 # does not exist, failed to load, etc.
00289                 continue
00290 
00291             # now check if we find our signature
00292             for line in contents.splitlines():
00293                 try:
00294                     id, s = line.split(None, 1)
00295                     id = int(id)
00296                 except ValueError:
00297                     continue
00298                 if s == sig:
00299                     result = self.get_id_url(report, id)
00300                     if not result:
00301                         # if we can't have an URL, just report as "known"
00302                         result = '1'
00303                     return result
00304 
00305         return None

Here is the call graph for this function:

Here is the caller graph for this function:

Return the ID of the most recently filed report.

Definition at line 214 of file memory.py.

00214 
00215     def latest_id(self):
00216         '''Return the ID of the most recently filed report.'''
00217 
00218         return len(self.reports) - 1

def apport.crashdb_impl.memory.CrashDatabase.mark_regression (   self,
  id,
  master 
)
Mark a crash id as reintroducing an earlier crash which is
already marked as fixed (having ID 'master').

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 178 of file memory.py.

00178 
00179     def mark_regression(self, id, master):
00180         '''Mark a crash id as reintroducing an earlier crash which is
00181         already marked as fixed (having ID 'master').'''
00182 
00183         assert self.reports[master]['fixed_version'] is not None
00184         self.reports[id]['comment'] = 'regression, already fixed in #%i' % master

def apport.crashdb.CrashDatabase.mark_retrace_failed (   self,
  id,
  invalid_msg = None 
) [inherited]
Mark crash id as 'failed to retrace'.

If invalid_msg is given, the bug should be closed as invalid with given
message, otherwise just marked as a failed retrace.

This can be a no-op if you are not interested in this.

Reimplemented in apport.crashdb_impl.launchpad.CrashDatabase.

Definition at line 777 of file crashdb.py.

00777 
00778     def mark_retrace_failed(self, id, invalid_msg=None):
00779         '''Mark crash id as 'failed to retrace'.
00780 
00781         If invalid_msg is given, the bug should be closed as invalid with given
00782         message, otherwise just marked as a failed retrace.
00783 
00784         This can be a no-op if you are not interested in this.
00785         '''
00786         raise NotImplementedError('this method must be implemented by a concrete subclass')

Mark crash id as retraced.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 193 of file memory.py.

00193 
00194     def mark_retraced(self, id):
00195         '''Mark crash id as retraced.'''
00196 
00197         self.unretraced.remove(id)

def apport.crashdb.CrashDatabase.set_credentials (   self,
  username,
  password 
) [inherited]
Set username and password.

Definition at line 680 of file crashdb.py.

00680 
00681     def set_credentials(self, username, password):
00682         '''Set username and password.'''
00683 
00684         raise NotImplementedError('this method must be implemented by a concrete subclass')

def apport.crashdb_impl.memory.CrashDatabase.update (   self,
  id,
  report,
  comment,
  change_description = False,
  attachment_comment = None,
  key_filter = None 
)
Update the given report ID with all data from report.

This creates a text comment with the "short" data (see
ProblemReport.write_mime()), and creates attachments for all the
bulk/binary data.

If change_description is True, and the crash db implementation supports
it, the short data will be put into the description instead (like in a
new bug).

comment will be added to the "short" data. If attachment_comment is
given, it will be added to the attachment uploads.

If key_filter is a list or set, then only those keys will be added.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 97 of file memory.py.

00097 
00098                attachment_comment=None, key_filter=None):
00099         '''Update the given report ID with all data from report.
00100 
00101         This creates a text comment with the "short" data (see
00102         ProblemReport.write_mime()), and creates attachments for all the
00103         bulk/binary data.
00104 
00105         If change_description is True, and the crash db implementation supports
00106         it, the short data will be put into the description instead (like in a
00107         new bug).
00108 
00109         comment will be added to the "short" data. If attachment_comment is
00110         given, it will be added to the attachment uploads.
00111 
00112         If key_filter is a list or set, then only those keys will be added.
00113         '''
00114         r = self.reports[id]
00115         r['comment'] = comment
00116 
00117         if key_filter:
00118             for f in key_filter:
00119                 if f in report:
00120                     r['report'][f] = report[f]
00121         else:
00122             r['report'].update(report)

Here is the caller graph for this function:

def apport.crashdb.CrashDatabase.update_traces (   self,
  id,
  report,
  comment = '' 
) [inherited]
Update the given report ID for retracing results.

This updates Stacktrace, ThreadStacktrace, StacktraceTop,
and StacktraceSource. You can also supply an additional comment.

Reimplemented in apport.crashdb_impl.launchpad.CrashDatabase.

Definition at line 671 of file crashdb.py.

00671 
00672     def update_traces(self, id, report, comment=''):
00673         '''Update the given report ID for retracing results.
00674 
00675         This updates Stacktrace, ThreadStacktrace, StacktraceTop,
00676         and StacktraceSource. You can also supply an additional comment.
00677         '''
00678         self.update(id, report, comment, key_filter=[
00679             'Stacktrace', 'ThreadStacktrace', 'StacktraceSource', 'StacktraceTop'])

Here is the call graph for this function:

Here is the caller graph for this function:

def apport.crashdb_impl.memory.CrashDatabase.upload (   self,
  report,
  progress_callback = None 
)
Store the report and return a handle number (starting from 0).

This does not support (nor need) progress callbacks.

Reimplemented from apport.crashdb.CrashDatabase.

Definition at line 36 of file memory.py.

00036 
00037     def upload(self, report, progress_callback=None):
00038         '''Store the report and return a handle number (starting from 0).
00039 
00040         This does not support (nor need) progress callbacks.
00041         '''
00042         assert self.accepts(report)
00043 
00044         self.reports.append({'report': report, 'fixed_version': None, 'dup_of':
00045                              None, 'comment:': ''})
00046         id = len(self.reports) - 1
00047         if 'Traceback' in report:
00048             self.dup_unchecked.add(id)
00049         else:
00050             self.unretraced.add(id)
00051         return id

Here is the call graph for this function:


Member Data Documentation

Definition at line 48 of file crashdb.py.

Definition at line 31 of file memory.py.

Definition at line 50 of file crashdb.py.

Definition at line 87 of file crashdb.py.

Reimplemented in apport.crashdb_impl.launchpad.CrashDatabase.

Definition at line 49 of file crashdb.py.

Definition at line 29 of file memory.py.

Definition at line 30 of file memory.py.


The documentation for this class was generated from the following file: