~apport-hackers/apport/main

3510 by Benjamin Drung
Format Python code with black
1
"""Crash database implementation for Launchpad."""
1679 by Martin Pitt
Update all copyright and description headers and consistently format them.
2
3
# Copyright (C) 2007 - 2009 Canonical Ltd.
3510 by Benjamin Drung
Format Python code with black
4
# Authors: Martin Pitt <martin.pitt@ubuntu.com>
5
#          Markus Korn <thekorn@gmx.de>
2189 by Martin Pitt
remove trailing whitespace
6
#
1679 by Martin Pitt
Update all copyright and description headers and consistently format them.
7
# This program is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by the
9
# Free Software Foundation; either version 2 of the License, or (at your
10
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
11
# the full text of the license.
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
12
3497 by Benjamin Drung
Sort Python imports with isort
13
import atexit
14
import email
15
import gzip
3536 by Benjamin Drung
Do not use from-imports for standard libraries
16
import http.client
17
import io
3497 by Benjamin Drung
Sort Python imports with isort
18
import os.path
19
import re
20
import shutil
21
import sys
22
import tempfile
23
import time
3536 by Benjamin Drung
Do not use from-imports for standard libraries
24
import urllib.parse
25
import urllib.request
3497 by Benjamin Drung
Sort Python imports with isort
26
27
from httplib2 import FailedToDecompressContent
2349 by Martin Pitt
* launchpad.py: Also work with Python 3. Deal gracefully with a missing "launchpadlib" module; this is not yet available for Python 3, but not required for client-side reporting.
28
29
try:
3623 by Benjamin Drung
Fix pylints broad-except (where possible)
30
    from launchpadlib.errors import HTTPError, RestfulError
2349 by Martin Pitt
* launchpad.py: Also work with Python 3. Deal gracefully with a missing "launchpadlib" module; this is not yet available for Python 3, but not required for client-side reporting.
31
    from launchpadlib.launchpad import Launchpad
32
except ImportError:
33
    # if launchpadlib is not available, only client-side reporting will work
34
    Launchpad = None
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
35
3497 by Benjamin Drung
Sort Python imports with isort
36
import apport
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
37
import apport.crashdb
38
3510 by Benjamin Drung
Format Python code with black
39
default_credentials_path = os.path.expanduser(
40
    "~/.cache/apport/launchpad.credentials"
41
)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
42
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
43
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
44
def filter_filename(attachments):
45
    for attachment in attachments:
1748 by Martin Pitt
launchpad.py: Some LP bugs have broken attachments (this is a bug in Launchpad itself). Ignore those instead of crashing.
46
        try:
47
            f = attachment.data.open()
3152 by Brian Murray
apport/crashdb_imply/launchpad.py: Handle FailedToDecompressContent httplib2 error when downloading a broken attachment.
48
        except (HTTPError, FailedToDecompressContent):
3510 by Benjamin Drung
Format Python code with black
49
            apport.error("Broken attachment on bug, ignoring")
1748 by Martin Pitt
launchpad.py: Some LP bugs have broken attachments (this is a bug in Launchpad itself). Ignore those instead of crashing.
50
            continue
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
51
        name = f.filename
3510 by Benjamin Drung
Format Python code with black
52
        if name.endswith(".txt") or name.endswith(".gz"):
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
53
            yield f
2189 by Martin Pitt
remove trailing whitespace
54
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
55
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
56
def id_set(tasks):
57
    # same as set(int(i.bug.id) for i in tasks) but faster
3510 by Benjamin Drung
Format Python code with black
58
    return set(int(i.self_link.split("/").pop()) for i in tasks)
2189 by Martin Pitt
remove trailing whitespace
59
880 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Add function get_source_component() to
60
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
61
class CrashDatabase(apport.crashdb.CrashDatabase):
3510 by Benjamin Drung
Format Python code with black
62
    """Launchpad implementation of crash database interface."""
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
63
2089 by Martin Pitt
crashdb.py: Change CrashDatabase constructor to not explicitly take a "bugpattern_baseurl" argument any more. Just take it from the options dictionary.
64
    def __init__(self, auth, options):
3510 by Benjamin Drung
Format Python code with black
65
        """Initialize Launchpad crash database.
2189 by Martin Pitt
remove trailing whitespace
66
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
67
        You need to specify a launchpadlib-style credentials file to
68
        access launchpad. If you supply None, it will use
69
        default_credentials_path (~/.cache/apport/launchpad.credentials).
1564 by Martin Pitt
launchpad.py: Add "cache_dir" option and $APPORT_LAUNCHPAD_CACHE environment variable to specify a non-temporary cache directory. (LP: #416804)
70
71
        Recognized options are:
1621 by Martin Pitt
improve documentation of crash database options
72
        - distro: Name of the distribution in Launchpad
73
        - project: Name of the project in Launchpad
3585 by Benjamin Drung
Fix indentation of list for CrashDatabase
74
          (Note that exactly one of "distro" or "project" must be given.)
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
75
        - launchpad_instance: If set, this uses the given launchpad instance
76
          instead of production (optional). This can be overriden or set by
3586 by Benjamin Drung
doc: Replace APPORT_STAGING by APPORT_LAUNCHPAD_INSTANCE
77
          $APPORT_LAUNCHPAD_INSTANCE environment. For example: "qastaging" or
78
          "staging".
1621 by Martin Pitt
improve documentation of crash database options
79
        - cache_dir: Path to a permanent cache directory; by default it uses a
1564 by Martin Pitt
launchpad.py: Add "cache_dir" option and $APPORT_LAUNCHPAD_CACHE environment variable to specify a non-temporary cache directory. (LP: #416804)
80
          temporary one. (optional). This can be overridden or set by
81
          $APPORT_LAUNCHPAD_CACHE environment.
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
82
        - escalation_subscription: This subscribes the given person or team to
1705 by Martin Pitt
launchpad.py: Do not keep escalating bugs, just escalate at the 10th duplicate.
83
          a bug once it gets the 10th duplicate.
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
84
        - escalation_tag: This adds the given tag to a bug once it gets more
85
          than 10 duplicates.
2329 by Martin Pitt
* launchpad.py: Make Launchpad bug subscription user/team configurable: The initial subscriber after filing a bug can be set with the "initial_subscriber" crashdb option, and the team which gets subscribed after retracing with "triaging_team". (LP: #980726)
86
        - initial_subscriber: The Launchpad user which gets subscribed to newly
87
          filed bugs (default: "apport"). It should be a bot user which the
88
          crash-digger instance runs as, as this will get to see all bug
89
          details immediately.
90
        - triaging_team: The Launchpad user/team which gets subscribed after
91
          updating a crash report bug by the retracer (default:
92
          "ubuntu-crashes-universe")
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
93
        - architecture: If set, this sets and watches out for needs-*-retrace
94
          tags of this architecture. This is useful when being used with
95
          apport-retrace and crash-digger to process crash reports of foreign
96
          architectures. Defaults to system architecture.
3510 by Benjamin Drung
Format Python code with black
97
        """
98
        if os.getenv("APPORT_LAUNCHPAD_INSTANCE"):
99
            options["launchpad_instance"] = os.getenv(
100
                "APPORT_LAUNCHPAD_INSTANCE"
101
            )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
102
        if not auth:
3510 by Benjamin Drung
Format Python code with black
103
            lp_instance = options.get("launchpad_instance")
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
104
            if lp_instance:
3510 by Benjamin Drung
Format Python code with black
105
                auth = (
106
                    default_credentials_path
107
                    + "."
108
                    + lp_instance.split("://", 1)[-1]
109
                )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
110
            else:
111
                auth = default_credentials_path
2089 by Martin Pitt
crashdb.py: Change CrashDatabase constructor to not explicitly take a "bugpattern_baseurl" argument any more. Just take it from the options dictionary.
112
        apport.crashdb.CrashDatabase.__init__(self, auth, options)
770.1.15 by Martin Pitt
move Launchpad distro name to crashdb.conf, generalize option passing
113
3510 by Benjamin Drung
Format Python code with black
114
        self.distro = options.get("distro")
1620 by Martin Pitt
launchpad.py: Assert that we have exactly one of "distro" or "project" option
115
        if self.distro:
3510 by Benjamin Drung
Format Python code with black
116
            assert (
117
                "project" not in options
118
            ), 'Must not set both "project" and "distro" option'
1620 by Martin Pitt
launchpad.py: Assert that we have exactly one of "distro" or "project" option
119
        else:
3510 by Benjamin Drung
Format Python code with black
120
            assert (
121
                "project" in options
122
            ), 'Need to have either "project" or "distro" option'
1620 by Martin Pitt
launchpad.py: Assert that we have exactly one of "distro" or "project" option
123
3510 by Benjamin Drung
Format Python code with black
124
        if "architecture" in options:
125
            self.arch_tag = "need-%s-retrace" % options["architecture"]
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
126
        else:
3510 by Benjamin Drung
Format Python code with black
127
            self.arch_tag = (
128
                "need-%s-retrace" % apport.packaging.get_system_architecture()
129
            )
1324 by Martin Pitt
apport/crashdb_impl/launchpad.py: Fully enable operation with
130
        self.options = options
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
131
        self.auth = auth
132
        assert self.auth
133
134
        self.__launchpad = None
135
        self.__lp_distro = None
3510 by Benjamin Drung
Format Python code with black
136
        self.__lpcache = os.getenv(
137
            "APPORT_LAUNCHPAD_CACHE", options.get("cache_dir")
138
        )
2775 by Martin Pitt
* launchpad: Really use a temporary launchpadlib cache dir by default. This avoids piling up gigabytes of useless cached data over time, which also tends to break every now and then.
139
        if not self.__lpcache:
140
            # use a temporary dir
3510 by Benjamin Drung
Format Python code with black
141
            self.__lpcache = tempfile.mkdtemp(prefix="launchpadlib.cache.")
2780 by Martin Pitt
* Fix FileNotFoundError from temporary launchpadlib cache dir cleanup. (LP: #1300474)
142
            atexit.register(shutil.rmtree, self.__lpcache, ignore_errors=True)
2189 by Martin Pitt
remove trailing whitespace
143
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
144
    @property
145
    def launchpad(self):
3510 by Benjamin Drung
Format Python code with black
146
        """Return Launchpad instance."""
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
147
148
        if self.__launchpad:
149
            return self.__launchpad
150
2349 by Martin Pitt
* launchpad.py: Also work with Python 3. Deal gracefully with a missing "launchpadlib" module; this is not yet available for Python 3, but not required for client-side reporting.
151
        if Launchpad is None:
3510 by Benjamin Drung
Format Python code with black
152
            sys.stderr.write(
153
                "ERROR: The launchpadlib Python %s module is not installed."
3653 by Benjamin Drung
Suggest installing python3-launchpadlib if missing and needed
154
                " Please install the python3-launchpadlib package!\n"
155
                % sys.version[0]
3510 by Benjamin Drung
Format Python code with black
156
            )
2349 by Martin Pitt
* launchpad.py: Also work with Python 3. Deal gracefully with a missing "launchpadlib" module; this is not yet available for Python 3, but not required for client-side reporting.
157
            sys.exit(1)
158
3510 by Benjamin Drung
Format Python code with black
159
        if self.options.get("launchpad_instance"):
160
            launchpad_instance = self.options.get("launchpad_instance")
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
161
        else:
3510 by Benjamin Drung
Format Python code with black
162
            launchpad_instance = "production"
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
163
164
        auth_dir = os.path.dirname(self.auth)
1460 by Martin Pitt
launchpad.py: Fix crash if auth credentials are in current directory
165
        if auth_dir and not os.path.isdir(auth_dir):
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
166
            os.makedirs(auth_dir)
167
1420 by Martin Pitt
launchpad.py: consider socket errors when connecting as transient
168
        try:
3510 by Benjamin Drung
Format Python code with black
169
            self.__launchpad = Launchpad.login_with(
170
                "apport-collect",
171
                launchpad_instance,
172
                launchpadlib_dir=self.__lpcache,
173
                allow_access_levels=["WRITE_PRIVATE"],
174
                credentials_file=self.auth,
175
                version="1.0",
176
            )
3623 by Benjamin Drung
Fix pylints broad-except (where possible)
177
        except (RestfulError, OSError, ValueError) as error:
3510 by Benjamin Drung
Format Python code with black
178
            apport.error(
179
                "connecting to Launchpad failed: %s\n"
180
                'You can reset the credentials by removing the file "%s"',
3566 by Benjamin Drung
Use getattr() instead of hasattr() plus .member
181
                getattr(error, "content", str(error)),
3510 by Benjamin Drung
Format Python code with black
182
                self.auth,
183
            )
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
184
            sys.exit(99)  # transient error
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
185
186
        return self.__launchpad
187
188
    def _get_distro_tasks(self, tasks):
189
        if not self.distro:
3552 by Benjamin Drung
Fix pylint's stop-iteration-return
190
            return
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
191
192
        for t in tasks:
3510 by Benjamin Drung
Format Python code with black
193
            if t.bug_target_name.lower() == self.distro or re.match(
194
                r"^.+\(%s.*\)$" % self.distro, t.bug_target_name.lower()
195
            ):
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
196
                yield t
2189 by Martin Pitt
remove trailing whitespace
197
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
198
    @property
199
    def lp_distro(self):
200
        if self.__lp_distro is None:
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
201
            if self.distro:
202
                self.__lp_distro = self.launchpad.distributions[self.distro]
3510 by Benjamin Drung
Format Python code with black
203
            elif "project" in self.options:
204
                self.__lp_distro = self.launchpad.projects[
205
                    self.options["project"]
206
                ]
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
207
            else:
3510 by Benjamin Drung
Format Python code with black
208
                raise SystemError(
209
                    "distro or project needs to be specified"
210
                    " in crashdb options"
211
                )
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
212
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
213
        return self.__lp_distro
856 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Do a bogus call to Bug() in the ctor.
214
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
215
    def upload(self, report, progress_callback=None):
3510 by Benjamin Drung
Format Python code with black
216
        """Upload given problem report return a handle for it.
2189 by Martin Pitt
remove trailing whitespace
217
218
        This should happen noninteractively.
219
966 by Martin Pitt
* apport/crashdb.py: Add a second optional parameter to upload() to specify
220
        If the implementation supports it, and a function progress_callback is
221
        passed, that is called repeatedly with two arguments: the number of
222
        bytes already sent, and the total number of bytes to send. This can be
2260 by Martin Pitt
Add a new crash database option "problem_types" and a CrashDatabase method "accepts(report)". This can be used to stop uploading particular problem report types to that database. E. g. a distribution might decide to not get "Crash" reports any more after release. Document the new option in doc/crashdb-conf.txt. - ui.py: Do not upload a report if the crash database does not accept the report's type. This behaviour is not really correct, but necessary as long as we only support a single crashdb and have whoopsie hardcoded. Once we have multiple crash dbs, we need to not even present the data if none of the DBs wants the report. See LP #957177 for details. (LP: #968121)
223
        used to provide a proper upload progress indication on frontends.
3510 by Benjamin Drung
Format Python code with black
224
        """
2260 by Martin Pitt
Add a new crash database option "problem_types" and a CrashDatabase method "accepts(report)". This can be used to stop uploading particular problem report types to that database. E. g. a distribution might decide to not get "Crash" reports any more after release. Document the new option in doc/crashdb-conf.txt. - ui.py: Do not upload a report if the crash database does not accept the report's type. This behaviour is not really correct, but necessary as long as we only support a single crashdb and have whoopsie hardcoded. Once we have multiple crash dbs, we need to not even present the data if none of the DBs wants the report. See LP #957177 for details. (LP: #968121)
225
        assert self.accepts(report)
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
226
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
227
        blob_file = self._generate_upload_blob(report)
3510 by Benjamin Drung
Format Python code with black
228
        ticket = upload_blob(
229
            blob_file, progress_callback, hostname=self.get_hostname()
230
        )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
231
        blob_file.close()
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
232
        assert ticket
233
        return ticket
234
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
235
    def get_hostname(self):
3510 by Benjamin Drung
Format Python code with black
236
        """Return the hostname for the Launchpad instance."""
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
237
3510 by Benjamin Drung
Format Python code with black
238
        launchpad_instance = self.options.get("launchpad_instance")
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
239
        if launchpad_instance:
3580 by Benjamin Drung
Add support for qastaging.launchpad.net
240
            if launchpad_instance == "qastaging":
241
                hostname = "qastaging.launchpad.net"
242
            elif launchpad_instance == "staging":
3510 by Benjamin Drung
Format Python code with black
243
                hostname = "staging.launchpad.net"
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
244
            else:
3510 by Benjamin Drung
Format Python code with black
245
                hostname = "launchpad.dev"
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
246
        else:
3510 by Benjamin Drung
Format Python code with black
247
            hostname = "launchpad.net"
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
248
        return hostname
249
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
250
    def get_comment_url(self, report, handle):
3510 by Benjamin Drung
Format Python code with black
251
        """Return an URL that should be opened after report has been uploaded
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
252
        and upload() returned handle.
253
254
        Should return None if no URL should be opened (anonymous filing without
255
        user comments); in that case this function should do whichever
3510 by Benjamin Drung
Format Python code with black
256
        interactive steps it wants to perform."""
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
257
258
        args = {}
3510 by Benjamin Drung
Format Python code with black
259
        title = report.get("Title", report.standard_title())
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
260
        if title:
3510 by Benjamin Drung
Format Python code with black
261
            args["field.title"] = title
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
262
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
263
        hostname = self.get_hostname()
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
264
3510 by Benjamin Drung
Format Python code with black
265
        if "SnapSource" in report:
266
            project = report["SnapSource"]
3344 by Lukas Märdian
Automatically handle snaps
267
        else:
3510 by Benjamin Drung
Format Python code with black
268
            project = self.options.get("project")
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
269
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
270
        if not project:
3510 by Benjamin Drung
Format Python code with black
271
            if "SourcePackage" in report:
272
                return "https://bugs.%s/%s/+source/%s/+filebug/%s?%s" % (
273
                    hostname,
274
                    self.distro,
275
                    report["SourcePackage"],
276
                    handle,
3536 by Benjamin Drung
Do not use from-imports for standard libraries
277
                    urllib.parse.urlencode(args),
3510 by Benjamin Drung
Format Python code with black
278
                )
1237 by Martin Pitt
* apport/ui.py: Check environment variable APPORT_REPORT_THIRDPARTY
279
            else:
3510 by Benjamin Drung
Format Python code with black
280
                return "https://bugs.%s/%s/+filebug/%s?%s" % (
281
                    hostname,
282
                    self.distro,
283
                    handle,
3536 by Benjamin Drung
Do not use from-imports for standard libraries
284
                    urllib.parse.urlencode(args),
3510 by Benjamin Drung
Format Python code with black
285
                )
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
286
        else:
3510 by Benjamin Drung
Format Python code with black
287
            return "https://bugs.%s/%s/+filebug/%s?%s" % (
288
                hostname,
289
                project,
290
                handle,
3536 by Benjamin Drung
Do not use from-imports for standard libraries
291
                urllib.parse.urlencode(args),
3510 by Benjamin Drung
Format Python code with black
292
            )
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
293
3625 by Benjamin Drung
Fix pylints redefined-builtin
294
    def get_id_url(self, report, crash_id):
3510 by Benjamin Drung
Format Python code with black
295
        """Return URL for a given report ID.
2085 by Martin Pitt
Add "publish" dupdb-admin command which exports the duplicate database into a set of text files suitable for WWW publishing. Implement crashdb.py known() method to check this format, if the crash database is initialized with a "dupdb_url" option pointing to the exported database.
296
297
        The report is passed in case building the URL needs additional
298
        information from it, such as the SourcePackage name.
299
300
        Return None if URL is not available or cannot be determined.
3510 by Benjamin Drung
Format Python code with black
301
        """
3625 by Benjamin Drung
Fix pylints redefined-builtin
302
        return "https://bugs.launchpad.net/bugs/" + str(crash_id)
2085 by Martin Pitt
Add "publish" dupdb-admin command which exports the duplicate database into a set of text files suitable for WWW publishing. Implement crashdb.py known() method to check this format, if the crash database is initialized with a "dupdb_url" option pointing to the exported database.
303
3625 by Benjamin Drung
Fix pylints redefined-builtin
304
    def download(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
305
        """Download the problem report from given ID and return a Report."""
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
306
307
        report = apport.Report()
3625 by Benjamin Drung
Fix pylints redefined-builtin
308
        b = self.launchpad.bugs[crash_id]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
309
310
        # parse out fields from summary
3510 by Benjamin Drung
Format Python code with black
311
        m = re.search(r"(ProblemType:.*)$", b.description, re.S)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
312
        if not m:
3510 by Benjamin Drung
Format Python code with black
313
            m = re.search(r"^--- \r?$[\r\n]*(.*)", b.description, re.M | re.S)
314
        assert m, "bug description must contain standard apport format data"
315
316
        description = (
317
            m.group(1)
318
            .encode("UTF-8")
319
            .replace(b"\xc2\xa0", b" ")
320
            .replace(b"\r\n", b"\n")
321
        )
322
323
        if b"\n\n" in description:
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
324
            # this often happens, remove all empty lines between top and
325
            # 'Uname'
3510 by Benjamin Drung
Format Python code with black
326
            if b"Uname:" in description:
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
327
                # this will take care of bugs like LP #315728 where stuff
328
                # is added after the apport data
3510 by Benjamin Drung
Format Python code with black
329
                (part1, part2) = description.split(b"Uname:", 1)
330
                description = (
331
                    part1.replace(b"\n\n", b"\n")
332
                    + b"Uname:"
333
                    + part2.split(b"\n\n", 1)[0]
334
                )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
335
            else:
1453 by Martin Pitt
launchpad.py: More robust download(), fixes other part of LP: #382589
336
                # just parse out the Apport block; e. g. LP #269539
3510 by Benjamin Drung
Format Python code with black
337
                description = description.split(b"\n\n", 1)[0]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
338
3536 by Benjamin Drung
Do not use from-imports for standard libraries
339
        report.load(io.BytesIO(description))
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
340
3510 by Benjamin Drung
Format Python code with black
341
        if "Date" not in report:
1414 by Martin Pitt
launchpad.py: Send and read Date: field again, reverting r1128; it is useful after all. (LP: #349139)
342
            # We had not submitted this field for a while, claiming it
343
            # redundant. But it is indeed required for up-to-the-minute
344
            # comparison with log files, etc. For backwards compatibility with
345
            # those reported bugs, read the creation date
346
            try:
3510 by Benjamin Drung
Format Python code with black
347
                report["Date"] = b.date_created.ctime()
1414 by Martin Pitt
launchpad.py: Send and read Date: field again, reverting r1128; it is useful after all. (LP: #349139)
348
            except AttributeError:
349
                # support older wadllib API which returned strings
3510 by Benjamin Drung
Format Python code with black
350
                report["Date"] = b.date_created
351
        if "ProblemType" not in report:
352
            if "apport-bug" in b.tags:
353
                report["ProblemType"] = "Bug"
354
            elif "apport-crash" in b.tags:
355
                report["ProblemType"] = "Crash"
356
            elif "apport-kernelcrash" in b.tags:
357
                report["ProblemType"] = "KernelCrash"
358
            elif "apport-package" in b.tags:
359
                report["ProblemType"] = "Package"
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
360
            else:
3510 by Benjamin Drung
Format Python code with black
361
                raise ValueError(
362
                    "cannot determine ProblemType from tags: " + str(b.tags)
363
                )
364
365
        report["Tags"] = " ".join(b.tags)
366
367
        if "Title" in report:
368
            report["OriginalTitle"] = report["Title"]
369
370
        report["Title"] = b.title
1449 by Martin Pitt
launchpad.py: Do not overwrite report['Title']
371
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
372
        for attachment in filter_filename(b.attachments):
373
            key, ext = os.path.splitext(attachment.filename)
1732 by Martin Pitt
launchpad.py, download(): Ignore attachments with invalid key names.
374
            # ignore attachments with invalid keys
375
            try:
3510 by Benjamin Drung
Format Python code with black
376
                report[key] = ""
3623 by Benjamin Drung
Fix pylints broad-except (where possible)
377
            except (AssertionError, TypeError, ValueError):
1732 by Martin Pitt
launchpad.py, download(): Ignore attachments with invalid key names.
378
                continue
3510 by Benjamin Drung
Format Python code with black
379
            if ext == ".txt":
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
380
                report[key] = attachment.read()
2915 by Martin Pitt
* launchpad backend download(): Try to convert textual values from byte arrays into into strings.
381
                try:
3510 by Benjamin Drung
Format Python code with black
382
                    report[key] = report[key].decode("UTF-8")
2915 by Martin Pitt
* launchpad backend download(): Try to convert textual values from byte arrays into into strings.
383
                except UnicodeDecodeError:
384
                    pass
3510 by Benjamin Drung
Format Python code with black
385
            elif ext == ".gz":
1775 by Martin Pitt
launchpad.py: Fix crash on attachments which are named *.gz, but uncompressed. (LP: #574360)
386
                try:
387
                    report[key] = gzip.GzipFile(fileobj=attachment).read()
3553 by Benjamin Drung
Rename IOError to OSError
388
                except OSError as error:
1775 by Martin Pitt
launchpad.py: Fix crash on attachments which are named *.gz, but uncompressed. (LP: #574360)
389
                    # some attachments are only called .gz, but are
390
                    # uncompressed (LP #574360)
3548 by Benjamin Drung
Rename Exception variable from e to error
391
                    if "Not a gzip" not in str(error):
1775 by Martin Pitt
launchpad.py: Fix crash on attachments which are named *.gz, but uncompressed. (LP: #574360)
392
                        raise
393
                    attachment.seek(0)
394
                    report[key] = attachment.read()
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
395
            else:
3510 by Benjamin Drung
Format Python code with black
396
                raise Exception(
397
                    "Unknown attachment type: " + attachment.filename
398
                )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
399
        return report
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
400
3510 by Benjamin Drung
Format Python code with black
401
    def update(
402
        self,
3625 by Benjamin Drung
Fix pylints redefined-builtin
403
        crash_id,
3510 by Benjamin Drung
Format Python code with black
404
        report,
405
        comment,
406
        change_description=False,
407
        attachment_comment=None,
408
        key_filter=None,
409
    ):
410
        """Update the given report ID with all data from report.
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
411
412
        This creates a text comment with the "short" data (see
413
        ProblemReport.write_mime()), and creates attachments for all the
2189 by Martin Pitt
remove trailing whitespace
414
        bulk/binary data.
415
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
416
        If change_description is True, and the crash db implementation supports
417
        it, the short data will be put into the description instead (like in a
418
        new bug).
419
420
        comment will be added to the "short" data. If attachment_comment is
421
        given, it will be added to the attachment uploads.
1660 by Martin Pitt
add key filtering to to CrashDatabase.update()
422
423
        If key_filter is a list or set, then only those keys will be added.
3510 by Benjamin Drung
Format Python code with black
424
        """
3625 by Benjamin Drung
Fix pylints redefined-builtin
425
        bug = self.launchpad.bugs[crash_id]
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
426
3146 by Brian Murray
apport/crashdb_impl/launchpad.py: add a TODO, update NEWS.
427
        # TODO: raise an error if key_filter is not a list or set
1660 by Martin Pitt
add key filtering to to CrashDatabase.update()
428
        if key_filter:
429
            skip_keys = set(report.keys()) - set(key_filter)
430
        else:
431
            skip_keys = None
432
3510 by Benjamin Drung
Format Python code with black
433
        # we want to reuse the knowledge of write_mime() with all its
434
        # different input types and output formatting; however, we have to
435
        # dissect the mime ourselves, since we can't just upload it as a blob
3595 by Benjamin Drung
Fix pylints consider-using-with
436
        with tempfile.TemporaryFile() as mime:
437
            report.write_mime(mime, skip_keys=skip_keys)
438
            mime.flush()
439
            mime.seek(0)
440
            msg = email.message_from_binary_file(mime)
441
            msg_iter = msg.walk()
442
443
            # first part is the multipart container
444
            part = next(msg_iter)
445
            assert part.is_multipart()
446
447
            # second part should be an inline text/plain attachments with
448
            # all short fields
449
            part = next(msg_iter)
450
            assert not part.is_multipart()
451
            assert part.get_content_type() == "text/plain"
452
453
            if not key_filter:
454
                # when we update a complete report, we are updating
455
                # an existing bug with apport-collect
456
                x = bug.tags[:]  # LP#254901 workaround
457
                x.append("apport-collected")
458
                # add any tags (like the release) to the bug
459
                if "Tags" in report:
460
                    x += self._filter_tag_names(report["Tags"]).split()
461
                bug.tags = x
3145 by Brian Murray
apport/crashdb_impl/launchpad.py: Don't make a contentless change to a bug, it just generates more bug mail. See bug 1697911 in the ubuntu-bugs mbox for 2017-06 if you want an example.
462
                bug.lp_save()
3595 by Benjamin Drung
Fix pylints consider-using-with
463
                # fresh bug object, LP#336866 workaround
3625 by Benjamin Drung
Fix pylints redefined-builtin
464
                bug = self.launchpad.bugs[crash_id]
3595 by Benjamin Drung
Fix pylints consider-using-with
465
466
            # short text data
467
            text = part.get_payload(decode=True).decode("UTF-8", "replace")
468
            # text can be empty if you are only adding an attachment to a bug
469
            if text:
470
                if change_description:
471
                    bug.description = bug.description + "\n--- \n" + text
472
                    bug.lp_save()
473
                else:
474
                    if not comment:
475
                        comment = bug.title
476
                    bug.newMessage(content=text, subject=comment)
477
478
            # other parts are the attachments:
479
            for part in msg_iter:
480
                bug.addAttachment(
481
                    comment=attachment_comment or "",
482
                    description=part.get_filename(),
483
                    content_type=None,
484
                    data=part.get_payload(decode=True),
485
                    filename=part.get_filename(),
486
                    is_patch=False,
487
                )
2913 by Martin Pitt
* launchpad backend: Fix unclosed file in upload().
488
3625 by Benjamin Drung
Fix pylints redefined-builtin
489
    def update_traces(self, crash_id, report, comment=""):
3510 by Benjamin Drung
Format Python code with black
490
        """Update the given report ID for retracing results.
2189 by Martin Pitt
remove trailing whitespace
491
1661 by Martin Pitt
simplify update_traces() implementation by using update() with key filter
492
        This updates Stacktrace, ThreadStacktrace, StacktraceTop,
493
        and StacktraceSource. You can also supply an additional comment.
3510 by Benjamin Drung
Format Python code with black
494
        """
3625 by Benjamin Drung
Fix pylints redefined-builtin
495
        apport.crashdb.CrashDatabase.update_traces(
496
            self, crash_id, report, comment
497
        )
1661 by Martin Pitt
simplify update_traces() implementation by using update() with key filter
498
3625 by Benjamin Drung
Fix pylints redefined-builtin
499
        bug = self.launchpad.bugs[crash_id]
1741 by Martin Pitt
launchpad.py: Fix reassignment to right source package, and update bug filing for current staging (broken right now, though)
500
        # ensure it's assigned to a package
3510 by Benjamin Drung
Format Python code with black
501
        if "SourcePackage" in report:
1741 by Martin Pitt
launchpad.py: Fix reassignment to right source package, and update bug filing for current staging (broken right now, though)
502
            for task in bug.bug_tasks:
3510 by Benjamin Drung
Format Python code with black
503
                if task.target.resource_type_link.endswith("#distribution"):
504
                    task.target = self.lp_distro.getSourcePackage(
505
                        name=report["SourcePackage"]
506
                    )
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
507
                    task.lp_save()
3625 by Benjamin Drung
Fix pylints redefined-builtin
508
                    bug = self.launchpad.bugs[crash_id]
1741 by Martin Pitt
launchpad.py: Fix reassignment to right source package, and update bug filing for current staging (broken right now, though)
509
                    break
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
510
873 by Martin Pitt
* apport/crashdb_impl/launchpad.py, update(): Delete CoreDump.gz attachment
511
        # remove core dump if stack trace is usable
1278 by Martin Pitt
apport/crashdb_impl/launchpad.py: Consider an useful stack trace
512
        if report.has_useful_stacktrace():
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
513
            for a in bug.attachments:
3510 by Benjamin Drung
Format Python code with black
514
                if a.title == "CoreDump.gz":
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
515
                    try:
516
                        a.removeFromBug()
517
                    except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
518
                        pass  # LP#249950 workaround
1338 by Martin Pitt
apport/crashdb_impl/launchpad.py, update(): Intercept and ignore IOErrors
519
            try:
2893 by Martin Pitt
* launchpad backend: Work with Python 3, now that launchpadlib exists for Python 3. (LP: #1153671)
520
                task = self._get_distro_tasks(bug.bug_tasks)
3383 by Benjamin Drung
Drop Python 2 support
521
                task = next(task)
3510 by Benjamin Drung
Format Python code with black
522
                if task.importance == "Undecided":
523
                    task.importance = "Medium"
2116 by Martin Pitt
launchpad.py: Only set bug task importance if it is undecided.
524
                    task.lp_save()
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
525
            except StopIteration:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
526
                pass  # no distro tasks
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
527
528
            # update bug title with retraced function name
529
            fn = report.stacktrace_top_function()
530
            if fn:
3510 by Benjamin Drung
Format Python code with black
531
                m = re.match(
532
                    r"^(.*crashed with SIG.* in )([^( ]+)(\(\).*$)", bug.title
533
                )
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
534
                if m and m.group(2) != fn:
535
                    bug.title = m.group(1) + fn + m.group(3)
536
                    try:
537
                        bug.lp_save()
538
                    except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
539
                        pass  # LP#336866 workaround
3625 by Benjamin Drung
Fix pylints redefined-builtin
540
                    bug = self.launchpad.bugs[crash_id]
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
541
887 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement private crash bug handling,
542
        self._subscribe_triaging_team(bug, report)
543
3625 by Benjamin Drung
Fix pylints redefined-builtin
544
    def get_distro_release(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
545
        """Get 'DistroRelease: <release>' from the given report ID and return
546
        it."""
3625 by Benjamin Drung
Fix pylints redefined-builtin
547
        bug = self.launchpad.bugs[crash_id]
3510 by Benjamin Drung
Format Python code with black
548
        m = re.search("DistroRelease: ([-a-zA-Z0-9.+/ ]+)", bug.description)
981.1.3 by Markus Korn
changed syntax of launchpad.py according to new py-lp-bugs.\nTODO:\n * work on the attachments.remove-func in py-lp-bugs\n * check for a better solution of reading '.gz'\n * moving .authentification within the code\n * TESTING
549
        if m:
550
            return m.group(1)
3510 by Benjamin Drung
Format Python code with black
551
        raise ValueError("URL does not contain DistroRelease: field")
770.1.12 by Martin Pitt
move specific bug tracker knowledge into conf file, add factory function get_crashdb()
552
3625 by Benjamin Drung
Fix pylints redefined-builtin
553
    def get_affected_packages(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
554
        """Return list of affected source packages for given ID."""
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
555
556
        bug_target_re = re.compile(
3510 by Benjamin Drung
Format Python code with black
557
            r"/%s/(?:(?P<suite>[^/]+)/)?\+source/(?P<source>[^/]+)$"
558
            % self.distro
559
        )
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
560
3625 by Benjamin Drung
Fix pylints redefined-builtin
561
        bug = self.launchpad.bugs[crash_id]
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
562
        result = []
563
564
        for task in bug.bug_tasks:
565
            match = bug_target_re.search(task.target.self_link)
566
            if not match:
567
                continue
3510 by Benjamin Drung
Format Python code with black
568
            if task.status in ("Invalid", "Won't Fix", "Fix Released"):
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
569
                continue
3510 by Benjamin Drung
Format Python code with black
570
            result.append(match.group("source"))
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
571
        return result
572
3625 by Benjamin Drung
Fix pylints redefined-builtin
573
    def is_reporter(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
574
        """Check whether the user is the reporter of given ID."""
1656 by Martin Pitt
New CrashDatabase API: is_reporter()
575
3625 by Benjamin Drung
Fix pylints redefined-builtin
576
        bug = self.launchpad.bugs[crash_id]
1656 by Martin Pitt
New CrashDatabase API: is_reporter()
577
        return bug.owner.name == self.launchpad.me.name
578
3625 by Benjamin Drung
Fix pylints redefined-builtin
579
    def can_update(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
580
        """Check whether the user is eligible to update a report.
1655 by Martin Pitt
New CrashDatabase API: can_update()
581
582
        A user should add additional information to an existing ID if (s)he is
583
        the reporter or subscribed, the bug is open, not a duplicate, etc. The
2538.2.1 by Brian Murray
if there is a 2nd occurrence of a reported crash remove existing .upload and .uploaded files
584
        exact policy and checks should be done according to the particular
1655 by Martin Pitt
New CrashDatabase API: can_update()
585
        implementation.
3510 by Benjamin Drung
Format Python code with black
586
        """
3625 by Benjamin Drung
Fix pylints redefined-builtin
587
        bug = self.launchpad.bugs[crash_id]
1667 by Martin Pitt
launchpad.py, can_update(): Check if subscribed
588
        if bug.duplicate_of:
589
            return False
590
591
        if bug.owner.name == self.launchpad.me.name:
592
            return True
593
594
        # check subscription
1669 by Martin Pitt
launchpad.py, can_update(): Dramatically speed up subscription check
595
        me = self.launchpad.me.self_link
596
        for sub in bug.subscriptions.entries:
3510 by Benjamin Drung
Format Python code with black
597
            if sub["person_link"] == me:
1667 by Martin Pitt
launchpad.py, can_update(): Check if subscribed
598
                return True
599
600
        return False
1655 by Martin Pitt
New CrashDatabase API: can_update()
601
839 by Martin Pitt
* apport/crashdb.py: Add two new abstract methods get_unretraced() and
602
    def get_unretraced(self):
3510 by Benjamin Drung
Format Python code with black
603
        """Return an ID set of all crashes which have not been retraced yet and
604
        which happened on the current host architecture."""
1925 by Martin Pitt
launchpad.py: When searchTasks() times out, exit with 99 as this is a transient error.
605
        try:
3510 by Benjamin Drung
Format Python code with black
606
            bugs = self.lp_distro.searchTasks(
607
                tags=self.arch_tag, created_since="2011-08-01"
608
            )
1926 by Martin Pitt
launchpad.py: followup fix: also cover id_set() call in transient timeout check
609
            return id_set(bugs)
3623 by Benjamin Drung
Fix pylints broad-except (where possible)
610
        except HTTPError as error:
3548 by Benjamin Drung
Rename Exception variable from e to error
611
            apport.error("connecting to Launchpad failed: %s", str(error))
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
612
            sys.exit(99)  # transient error
839 by Martin Pitt
* apport/crashdb.py: Add two new abstract methods get_unretraced() and
613
840 by Martin Pitt
apport/crashdb.py: add get_dup_unchecked() and mark_dup_checked(), and implement it for launchpad
614
    def get_dup_unchecked(self):
3510 by Benjamin Drung
Format Python code with black
615
        """Return an ID set of all crashes which have not been checked for
840 by Martin Pitt
apport/crashdb.py: add get_dup_unchecked() and mark_dup_checked(), and implement it for launchpad
616
        being a duplicate.
617
618
        This is mainly useful for crashes of scripting languages such as
619
        Python, since they do not need to be retraced. It should not return
3510 by Benjamin Drung
Format Python code with black
620
        bugs that are covered by get_unretraced()."""
2189 by Martin Pitt
remove trailing whitespace
621
1925 by Martin Pitt
launchpad.py: When searchTasks() times out, exit with 99 as this is a transient error.
622
        try:
3510 by Benjamin Drung
Format Python code with black
623
            bugs = self.lp_distro.searchTasks(
624
                tags="need-duplicate-check", created_since="2011-08-01"
625
            )
1926 by Martin Pitt
launchpad.py: followup fix: also cover id_set() call in transient timeout check
626
            return id_set(bugs)
3623 by Benjamin Drung
Fix pylints broad-except (where possible)
627
        except HTTPError as error:
3548 by Benjamin Drung
Rename Exception variable from e to error
628
            apport.error("connecting to Launchpad failed: %s", str(error))
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
629
            sys.exit(99)  # transient error
840 by Martin Pitt
apport/crashdb.py: add get_dup_unchecked() and mark_dup_checked(), and implement it for launchpad
630
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
631
    def get_unfixed(self):
3510 by Benjamin Drung
Format Python code with black
632
        """Return an ID set of all crashes which are not yet fixed.
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
633
634
        The list must not contain bugs which were rejected or duplicate.
2189 by Martin Pitt
remove trailing whitespace
635
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
636
        This function should make sure that the returned list is correct. If
637
        there are any errors with connecting to the crash database, it should
3553 by Benjamin Drung
Rename IOError to OSError
638
        raise an exception (preferably OSError)."""
2189 by Martin Pitt
remove trailing whitespace
639
3510 by Benjamin Drung
Format Python code with black
640
        bugs = self.lp_distro.searchTasks(tags="apport-crash")
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
641
        return id_set(bugs)
642
643
    def _get_source_version(self, package):
3510 by Benjamin Drung
Format Python code with black
644
        """Return the version of given source package in the latest release of
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
645
        given distribution.
646
2189 by Martin Pitt
remove trailing whitespace
647
        If 'distro' is None, we will look for a launchpad project .
3510 by Benjamin Drung
Format Python code with black
648
        """
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
649
        sources = self.lp_distro.main_archive.getPublishedSources(
650
            exact_match=True,
651
            source_name=package,
3510 by Benjamin Drung
Format Python code with black
652
            distro_series=self.lp_distro.current_series,
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
653
        )
654
        # first element is the latest one
655
        return sources[0].source_package_version
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
656
3625 by Benjamin Drung
Fix pylints redefined-builtin
657
    def get_fixed_version(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
658
        """Return the package version that fixes a given crash.
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
659
660
        Return None if the crash is not yet fixed, or an empty string if the
661
        crash is fixed, but it cannot be determined by which version. Return
662
        'invalid' if the crash report got invalidated, such as closed a
663
        duplicate or rejected.
664
665
        This function should make sure that the returned result is correct. If
666
        there are any errors with connecting to the crash database, it should
3553 by Benjamin Drung
Rename IOError to OSError
667
        raise an exception (preferably OSError).
3510 by Benjamin Drung
Format Python code with black
668
        """
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
669
        # do not do version tracking yet; for that, we need to get the current
670
        # distrorelease and the current package version in that distrorelease
671
        # (or, of course, proper version tracking in Launchpad itself)
2189 by Martin Pitt
remove trailing whitespace
672
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
673
        try:
3625 by Benjamin Drung
Fix pylints redefined-builtin
674
            b = self.launchpad.bugs[crash_id]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
675
        except KeyError:
3510 by Benjamin Drung
Format Python code with black
676
            return "invalid"
2189 by Martin Pitt
remove trailing whitespace
677
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
678
        if b.duplicate_of:
3510 by Benjamin Drung
Format Python code with black
679
            return "invalid"
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
680
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
681
        tasks = list(b.bug_tasks)  # just fetch it once
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
682
683
        if self.distro:
3510 by Benjamin Drung
Format Python code with black
684
            distro_identifier = "(%s)" % self.distro.lower()
685
            fixed_tasks = list(
686
                filter(
687
                    lambda task: task.status == "Fix Released"
688
                    and distro_identifier
689
                    in task.bug_target_display_name.lower(),
690
                    tasks,
691
                )
692
            )
2189 by Martin Pitt
remove trailing whitespace
693
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
694
            if not fixed_tasks:
3510 by Benjamin Drung
Format Python code with black
695
                fixed_distro = list(
696
                    filter(
697
                        lambda task: task.status == "Fix Released"
698
                        and task.bug_target_name.lower()
699
                        == self.distro.lower(),
700
                        tasks,
701
                    )
702
                )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
703
                if fixed_distro:
704
                    # fixed in distro inself (without source package)
3510 by Benjamin Drung
Format Python code with black
705
                    return ""
2189 by Martin Pitt
remove trailing whitespace
706
707
            if len(fixed_tasks) > 1:
3510 by Benjamin Drung
Format Python code with black
708
                apport.warning(
709
                    "There is more than one task fixed in %s %s,"
710
                    " using first one to determine fixed version",
711
                    self.distro,
3625 by Benjamin Drung
Fix pylints redefined-builtin
712
                    crash_id,
3510 by Benjamin Drung
Format Python code with black
713
                )
714
                return ""
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
715
716
            if fixed_tasks:
717
                task = fixed_tasks.pop()
1487 by Martin Pitt
launchpad.py, get_fixed_version(): Fix crash on obsolete source packages
718
                try:
3510 by Benjamin Drung
Format Python code with black
719
                    return self._get_source_version(
720
                        task.bug_target_display_name.split()[0]
721
                    )
1487 by Martin Pitt
launchpad.py, get_fixed_version(): Fix crash on obsolete source packages
722
                except IndexError:
723
                    # source does not exist any more
3510 by Benjamin Drung
Format Python code with black
724
                    return "invalid"
1702 by Martin Pitt
launchpad.py, get_fixed_version(): Do not consider a bug as invalid just because it has any invalid distro package task.
725
            else:
726
                # check if there only invalid ones
3510 by Benjamin Drung
Format Python code with black
727
                invalid_tasks = list(
728
                    filter(
729
                        lambda task: task.status
730
                        in ("Invalid", "Won't Fix", "Expired")
731
                        and distro_identifier
732
                        in task.bug_target_display_name.lower(),
733
                        tasks,
734
                    )
735
                )
1702 by Martin Pitt
launchpad.py, get_fixed_version(): Do not consider a bug as invalid just because it has any invalid distro package task.
736
                if invalid_tasks:
3510 by Benjamin Drung
Format Python code with black
737
                    non_invalid_tasks = list(
738
                        filter(
739
                            lambda task: task.status
740
                            not in ("Invalid", "Won't Fix", "Expired")
741
                            and distro_identifier
742
                            in task.bug_target_display_name.lower(),
743
                            tasks,
744
                        )
745
                    )
1702 by Martin Pitt
launchpad.py, get_fixed_version(): Do not consider a bug as invalid just because it has any invalid distro package task.
746
                    if not non_invalid_tasks:
3510 by Benjamin Drung
Format Python code with black
747
                        return "invalid"
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
748
        else:
3510 by Benjamin Drung
Format Python code with black
749
            fixed_tasks = list(
750
                filter(lambda task: task.status == "Fix Released", tasks)
751
            )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
752
            if fixed_tasks:
753
                # TODO: look for current series
3510 by Benjamin Drung
Format Python code with black
754
                return ""
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
755
            # check if there any invalid ones
3510 by Benjamin Drung
Format Python code with black
756
            if list(filter(lambda task: task.status == "Invalid", tasks)):
757
                return "invalid"
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
758
832.1.10 by Martin Pitt
apport/crashdb_impl/launchpad.py: Implement get_unfixed() and get_fixed_version()
759
        return None
832.1.7 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement mark_regression() and
760
3625 by Benjamin Drung
Fix pylints redefined-builtin
761
    def duplicate_of(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
762
        """Return master ID for a duplicate bug.
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
763
764
        If the bug is not a duplicate, return None.
3510 by Benjamin Drung
Format Python code with black
765
        """
3625 by Benjamin Drung
Fix pylints redefined-builtin
766
        b = self.launchpad.bugs[crash_id].duplicate_of
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
767
        if b:
768
            return b.id
769
        else:
770
            return None
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
771
3625 by Benjamin Drung
Fix pylints redefined-builtin
772
    def close_duplicate(self, report, crash_id, master_id):
3510 by Benjamin Drung
Format Python code with black
773
        """Mark a crash id as duplicate of given master ID.
2189 by Martin Pitt
remove trailing whitespace
774
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
775
        If master is None, id gets un-duplicated.
3510 by Benjamin Drung
Format Python code with black
776
        """
3625 by Benjamin Drung
Fix pylints redefined-builtin
777
        bug = self.launchpad.bugs[crash_id]
919 by Martin Pitt
* apport/crashdb_impl/launchpad.py, close_duplicate(): If the master bug is
778
1537 by Martin Pitt
launchpad.py: Add a comment when marking a bug as a duplicate.
779
        if master_id:
3510 by Benjamin Drung
Format Python code with black
780
            assert (
3625 by Benjamin Drung
Fix pylints redefined-builtin
781
                crash_id != master_id
782
            ), "cannot mark bug %s as a duplicate of itself" % str(crash_id)
1566 by Martin Pitt
launchpad.py, close_duplicate(): Add duplicate assertion
783
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
784
            # check whether the master itself is a dup
1537 by Martin Pitt
launchpad.py: Add a comment when marking a bug as a duplicate.
785
            master = self.launchpad.bugs[master_id]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
786
            if master.duplicate_of:
787
                master = master.duplicate_of
2095 by Martin Pitt
launchpad.py: Attach updated stack traces from a duplicate to the master bug if it failed retracing previously or has an "apport-request-retrace" tag. (LP: #869982)
788
                master_id = master.id
3625 by Benjamin Drung
Fix pylints redefined-builtin
789
                if master.id == crash_id:
1949 by Martin Pitt
launchpad.py: Fix crash in close_duplicate() if master bug was already marked as a duplicate of the examined bug.
790
                    # this happens if the bug was manually duped to a newer one
3510 by Benjamin Drung
Format Python code with black
791
                    apport.warning(
792
                        "Bug %i was manually marked as a dupe of newer bug %i,"
793
                        " not closing as duplicate",
3625 by Benjamin Drung
Fix pylints redefined-builtin
794
                        crash_id,
3510 by Benjamin Drung
Format Python code with black
795
                        master_id,
796
                    )
1949 by Martin Pitt
launchpad.py: Fix crash in close_duplicate() if master bug was already marked as a duplicate of the examined bug.
797
                    return
2189 by Martin Pitt
remove trailing whitespace
798
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
799
            for a in bug.attachments:
3510 by Benjamin Drung
Format Python code with black
800
                if a.title in (
801
                    "CoreDump.gz",
802
                    "Stacktrace.txt",
803
                    "ThreadStacktrace.txt",
804
                    "ProcMaps.txt",
805
                    "ProcStatus.txt",
806
                    "Registers.txt",
807
                    "Disassembly.txt",
808
                ):
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
809
                    try:
810
                        a.removeFromBug()
811
                    except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
812
                        pass  # LP#249950 workaround
919 by Martin Pitt
* apport/crashdb_impl/launchpad.py, close_duplicate(): If the master bug is
813
3510 by Benjamin Drung
Format Python code with black
814
            # fresh bug object, LP#336866 workaround
3625 by Benjamin Drung
Fix pylints redefined-builtin
815
            bug = self.launchpad.bugs[crash_id]
3510 by Benjamin Drung
Format Python code with black
816
            bug.newMessage(
817
                content="Thank you for taking the time to report this crash"
818
                " and helping to make this software better.  This particular"
819
                " crash has already been reported and is a duplicate of bug"
820
                " #%i, so is being marked as such.  Please look at the other"
821
                " bug report to see if there is any missing information that"
822
                " you can provide, or to see if there is a workaround for the"
823
                " bug.  Additionally, any further discussion regarding the bug"
824
                " should occur in the other report.  Please continue to report"
825
                " any other bugs you may find." % master_id,
826
                subject="This bug is a duplicate",
827
            )
1526.1.1 by Brian Murray
launchpad.py: add a comment when marking a bug as a duplicate
828
3625 by Benjamin Drung
Fix pylints redefined-builtin
829
            # refresh, LP#336866 workaround
830
            bug = self.launchpad.bugs[crash_id]
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
831
            if bug.private:
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
832
                bug.private = False
832.1.7 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement mark_regression() and
833
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
834
            # set duplicate last, since we cannot modify already dup'ed bugs
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
835
            if not bug.duplicate_of:
836
                bug.duplicate_of = master
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
837
2322.1.1 by Brian Murray
launchpad.py: copy tags from duplicate bug to the master bug
838
            # cache tags of master bug report instead of performing multiple
839
            # queries
840
            master_tags = master.tags
841
1705 by Martin Pitt
launchpad.py: Do not keep escalating bugs, just escalate at the 10th duplicate.
842
            if len(master.duplicates) == 10:
3510 by Benjamin Drung
Format Python code with black
843
                if (
844
                    "escalation_tag" in self.options
845
                    and self.options["escalation_tag"] not in master_tags
846
                    and self.options.get("escalated_tag", " invalid ")
847
                    not in master_tags
848
                ):
849
                    master.tags = master_tags + [
850
                        self.options["escalation_tag"]
851
                    ]  # LP#254901 workaround
2407 by Martin Pitt
* Fix PEP-8 violations picked up by latest pep8 checker.
852
                    master.lp_save()
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
853
3510 by Benjamin Drung
Format Python code with black
854
                if (
855
                    "escalation_subscription" in self.options
856
                    and self.options.get("escalated_tag", " invalid ")
857
                    not in master_tags
858
                ):
859
                    p = self.launchpad.people[
860
                        self.options["escalation_subscription"]
861
                    ]
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
862
                    master.subscribe(person=p)
2095 by Martin Pitt
launchpad.py: Attach updated stack traces from a duplicate to the master bug if it failed retracing previously or has an "apport-request-retrace" tag. (LP: #869982)
863
864
            # requesting updated stack trace?
3510 by Benjamin Drung
Format Python code with black
865
            if report.has_useful_stacktrace() and (
866
                "apport-request-retrace" in master_tags
867
                or "apport-failed-retrace" in master_tags
868
            ):
869
                self.update(
870
                    master_id,
871
                    report,
3625 by Benjamin Drung
Fix pylints redefined-builtin
872
                    "Updated stack trace from duplicate bug %i" % crash_id,
3510 by Benjamin Drung
Format Python code with black
873
                    key_filter=[
874
                        "Stacktrace",
875
                        "ThreadStacktrace",
876
                        "Package",
877
                        "Dependencies",
878
                        "ProcMaps",
879
                        "ProcCmdline",
880
                    ],
881
                )
2095 by Martin Pitt
launchpad.py: Attach updated stack traces from a duplicate to the master bug if it failed retracing previously or has an "apport-request-retrace" tag. (LP: #869982)
882
883
                master = self.launchpad.bugs[master_id]
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
884
                x = master.tags[:]  # LP#254901 workaround
2095 by Martin Pitt
launchpad.py: Attach updated stack traces from a duplicate to the master bug if it failed retracing previously or has an "apport-request-retrace" tag. (LP: #869982)
885
                try:
3510 by Benjamin Drung
Format Python code with black
886
                    x.remove("apport-failed-retrace")
2099 by Martin Pitt
launchpad.py: Fix exception on tag removal, and fix test suite
887
                except ValueError:
2095 by Martin Pitt
launchpad.py: Attach updated stack traces from a duplicate to the master bug if it failed retracing previously or has an "apport-request-retrace" tag. (LP: #869982)
888
                    pass
889
                try:
3510 by Benjamin Drung
Format Python code with black
890
                    x.remove("apport-request-retrace")
2099 by Martin Pitt
launchpad.py: Fix exception on tag removal, and fix test suite
891
                except ValueError:
2095 by Martin Pitt
launchpad.py: Attach updated stack traces from a duplicate to the master bug if it failed retracing previously or has an "apport-request-retrace" tag. (LP: #869982)
892
                    pass
893
                master.tags = x
894
                try:
895
                    master.lp_save()
896
                except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
897
                    pass  # LP#336866 workaround
2095 by Martin Pitt
launchpad.py: Attach updated stack traces from a duplicate to the master bug if it failed retracing previously or has an "apport-request-retrace" tag. (LP: #869982)
898
2322.1.2 by Brian Murray
create a white list of tags to copy and add in not obsolete or future releases to that that white list, only copy tags to the master bug that are said white list
899
            # white list of tags to copy from duplicates bugs to the master
3510 by Benjamin Drung
Format Python code with black
900
            tags_to_copy = ["bugpattern-needed"]
2335 by Martin Pitt
apport/crashdb_impl/launchpad.py: Fix undefined "ubuntu" variable
901
            for series in self.lp_distro.series:
3510 by Benjamin Drung
Format Python code with black
902
                if series.status not in [
903
                    "Active Development",
904
                    "Current Stable Release",
905
                    "Supported",
906
                    "Pre-release Freeze",
907
                ]:
2322.1.2 by Brian Murray
create a white list of tags to copy and add in not obsolete or future releases to that that white list, only copy tags to the master bug that are said white list
908
                    continue
909
                tags_to_copy.append(series.name)
2322.1.1 by Brian Murray
launchpad.py: copy tags from duplicate bug to the master bug
910
            # copy tags over from the duplicate bug to the master bug
911
            dupe_tags = set(bug.tags)
2322.1.2 by Brian Murray
create a white list of tags to copy and add in not obsolete or future releases to that that white list, only copy tags to the master bug that are said white list
912
            # reload master tags as they may have changed
2322.1.1 by Brian Murray
launchpad.py: copy tags from duplicate bug to the master bug
913
            master_tags = master.tags
914
            missing_tags = dupe_tags.difference(master_tags)
915
2322.1.2 by Brian Murray
create a white list of tags to copy and add in not obsolete or future releases to that that white list, only copy tags to the master bug that are said white list
916
            for tag in missing_tags:
917
                if tag in tags_to_copy:
918
                    master_tags.append(tag)
919
920
            master.tags = master_tags
2322.1.1 by Brian Murray
launchpad.py: copy tags from duplicate bug to the master bug
921
            master.lp_save()
922
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
923
        else:
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
924
            if bug.duplicate_of:
925
                bug.duplicate_of = None
926
3632 by Benjamin Drung
Ignore remaining protected-access pylint complaints
927
        # pylint: disable=protected-access
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
928
        if bug._dirty_attributes:  # LP#336866 workaround
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
929
            bug.lp_save()
1003 by Martin Pitt
* apport/crashdb_impl/launchpad.py, close_duplicate(): Explicitly set the
930
3625 by Benjamin Drung
Fix pylints redefined-builtin
931
    def mark_regression(self, crash_id, master):
3510 by Benjamin Drung
Format Python code with black
932
        """Mark a crash id as reintroducing an earlier crash which is
933
        already marked as fixed (having ID 'master')."""
2189 by Martin Pitt
remove trailing whitespace
934
3625 by Benjamin Drung
Fix pylints redefined-builtin
935
        bug = self.launchpad.bugs[crash_id]
3510 by Benjamin Drung
Format Python code with black
936
        bug.newMessage(
937
            content="This crash has the same stack trace characteristics as"
938
            " bug #%i. However, the latter was already fixed in an earlier"
939
            " package version than the one in this report. This might be"
940
            " a regression or because the problem is in a dependent package."
941
            % master,
942
            subject="Possible regression detected",
943
        )
3625 by Benjamin Drung
Fix pylints redefined-builtin
944
        # fresh bug object, LP#336866 workaround
945
        bug = self.launchpad.bugs[crash_id]
3510 by Benjamin Drung
Format Python code with black
946
        bug.tags = bug.tags + ["regression-retracer"]  # LP#254901 workaround
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
947
        bug.lp_save()
832.1.7 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement mark_regression() and
948
3625 by Benjamin Drung
Fix pylints redefined-builtin
949
    def mark_retraced(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
950
        """Mark crash id as retraced."""
839 by Martin Pitt
* apport/crashdb.py: Add two new abstract methods get_unretraced() and
951
3625 by Benjamin Drung
Fix pylints redefined-builtin
952
        bug = self.launchpad.bugs[crash_id]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
953
        if self.arch_tag in bug.tags:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
954
            x = bug.tags[:]  # LP#254901 workaround
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
955
            x.remove(self.arch_tag)
956
            bug.tags = x
1488 by Martin Pitt
launchpad.py: Add another workaround for the "412 Precondition failed" bug (LP#336866)
957
            try:
958
                bug.lp_save()
959
            except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
960
                pass  # LP#336866 workaround
839 by Martin Pitt
* apport/crashdb.py: Add two new abstract methods get_unretraced() and
961
3625 by Benjamin Drung
Fix pylints redefined-builtin
962
    def mark_retrace_failed(self, crash_id, invalid_msg=None):
3510 by Benjamin Drung
Format Python code with black
963
        """Mark crash id as 'failed to retrace'."""
871 by Martin Pitt
* apport/crashdb.py: Add interface mark_retrace_failed(). Implement it in
964
3625 by Benjamin Drung
Fix pylints redefined-builtin
965
        bug = self.launchpad.bugs[crash_id]
1210 by Martin Pitt
apport/crashdb.py, mark_retrace_failed(): Add new optional
966
        if invalid_msg:
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
967
            try:
2893 by Martin Pitt
* launchpad backend: Work with Python 3, now that launchpadlib exists for Python 3. (LP: #1153671)
968
                task = self._get_distro_tasks(bug.bug_tasks)
3383 by Benjamin Drung
Drop Python 2 support
969
                task = next(task)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
970
            except StopIteration:
971
                # no distro task, just use the first one
972
                task = bug.bug_tasks[0]
3510 by Benjamin Drung
Format Python code with black
973
            task.status = "Invalid"
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
974
            task.lp_save()
3510 by Benjamin Drung
Format Python code with black
975
            bug.newMessage(
976
                content=invalid_msg, subject="Crash report cannot be processed"
977
            )
2189 by Martin Pitt
remove trailing whitespace
978
1463 by Martin Pitt
launchpad.py: delete core dump from invalid bug reports
979
            for a in bug.attachments:
3510 by Benjamin Drung
Format Python code with black
980
                if a.title == "CoreDump.gz":
1463 by Martin Pitt
launchpad.py: delete core dump from invalid bug reports
981
                    try:
982
                        a.removeFromBug()
983
                    except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
984
                        pass  # LP#249950 workaround
1210 by Martin Pitt
apport/crashdb.py, mark_retrace_failed(): Add new optional
985
        else:
3510 by Benjamin Drung
Format Python code with black
986
            if "apport-failed-retrace" not in bug.tags:
987
                # LP#254901 workaround
988
                bug.tags = bug.tags + ["apport-failed-retrace"]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
989
                bug.lp_save()
871 by Martin Pitt
* apport/crashdb.py: Add interface mark_retrace_failed(). Implement it in
990
3625 by Benjamin Drung
Fix pylints redefined-builtin
991
    def _mark_dup_checked(self, crash_id, report):
3510 by Benjamin Drung
Format Python code with black
992
        """Mark crash id as checked for being a duplicate."""
840 by Martin Pitt
apport/crashdb.py: add get_dup_unchecked() and mark_dup_checked(), and implement it for launchpad
993
3625 by Benjamin Drung
Fix pylints redefined-builtin
994
        bug = self.launchpad.bugs[crash_id]
1632 by Martin Pitt
launchpad.py: If unset, set bug task source package also for interpreter crashes.
995
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
996
        # if we have a distro task without a package, fix it
3510 by Benjamin Drung
Format Python code with black
997
        if "SourcePackage" in report:
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
998
            for task in bug.bug_tasks:
3510 by Benjamin Drung
Format Python code with black
999
                if task.target.resource_type_link.endswith("#distribution"):
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
1000
                    task.target = self.lp_distro.getSourcePackage(
3510 by Benjamin Drung
Format Python code with black
1001
                        name=report["SourcePackage"]
1002
                    )
2635 by Martin Pitt
* launchpad.py: Fix crash when trying to adjust a distro-only bug task if the bug also already has a distropackage task.
1003
                    try:
1004
                        task.lp_save()
3625 by Benjamin Drung
Fix pylints redefined-builtin
1005
                        bug = self.launchpad.bugs[crash_id]
2635 by Martin Pitt
* launchpad.py: Fix crash when trying to adjust a distro-only bug task if the bug also already has a distropackage task.
1006
                    except HTTPError:
3510 by Benjamin Drung
Format Python code with black
1007
                        # might fail if there is already another
1008
                        # Ubuntu package task
2635 by Martin Pitt
* launchpad.py: Fix crash when trying to adjust a distro-only bug task if the bug also already has a distropackage task.
1009
                        pass
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
1010
                    break
1632 by Martin Pitt
launchpad.py: If unset, set bug task source package also for interpreter crashes.
1011
3510 by Benjamin Drung
Format Python code with black
1012
        if "need-duplicate-check" in bug.tags:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
1013
            x = bug.tags[:]  # LP#254901 workaround
3510 by Benjamin Drung
Format Python code with black
1014
            x.remove("need-duplicate-check")
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1015
            bug.tags = x
2189 by Martin Pitt
remove trailing whitespace
1016
            bug.lp_save()
3510 by Benjamin Drung
Format Python code with black
1017
            if "Traceback" in report:
2322.1.3 by Brian Murray
set the importance of bugs with a traceback to Medium just like bugs with coredumps
1018
                for task in bug.bug_tasks:
3510 by Benjamin Drung
Format Python code with black
1019
                    if "#distribution" in task.target.resource_type_link:
1020
                        if task.importance == "Undecided":
1021
                            task.importance = "Medium"
2322.1.3 by Brian Murray
set the importance of bugs with a traceback to Medium just like bugs with coredumps
1022
                            task.lp_save()
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1023
        self._subscribe_triaging_team(bug, report)
887 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement private crash bug handling,
1024
2098 by Martin Pitt
launchpad.py: Override known() to check if the master bug is actually accessible by the reporter, and is not tagged with "apport-failed-retrace" or "apport-request-retrace"; otherwise file it anyway.
1025
    def known(self, report):
3510 by Benjamin Drung
Format Python code with black
1026
        """Check if the crash db already knows about the crash signature.
2098 by Martin Pitt
launchpad.py: Override known() to check if the master bug is actually accessible by the reporter, and is not tagged with "apport-failed-retrace" or "apport-request-retrace"; otherwise file it anyway.
1027
1028
        Check if the report has a DuplicateSignature, crash_signature(), or
1029
        StacktraceAddressSignature, and ask the database whether the problem is
1030
        already known. If so, return an URL where the user can check the status
1031
        or subscribe (if available), or just return True if the report is known
1032
        but there is no public URL. In that case the report will not be
1033
        uploaded (i. e. upload() will not be called).
1034
1035
        Return None if the report does not have any signature or the crash
1036
        database does not support checking for duplicates on the client side.
1037
1038
        The default implementation uses a text file format generated by
1039
        duplicate_db_publish() at an URL specified by the "dupdb_url" option.
1040
        Subclasses are free to override this with a custom implementation, such
1041
        as a real database lookup.
3510 by Benjamin Drung
Format Python code with black
1042
        """
2098 by Martin Pitt
launchpad.py: Override known() to check if the master bug is actually accessible by the reporter, and is not tagged with "apport-failed-retrace" or "apport-request-retrace"; otherwise file it anyway.
1043
        # we override the method here to check if the user actually has access
1044
        # to the bug, and if the bug requests more retraces; in either case we
1045
        # should file it.
1046
        url = apport.crashdb.CrashDatabase.known(self, report)
1047
1048
        if not url:
1049
            return url
1050
1051
        # record the fact that it is a duplicate, for triagers
3510 by Benjamin Drung
Format Python code with black
1052
        report["DuplicateOf"] = url
2098 by Martin Pitt
launchpad.py: Override known() to check if the master bug is actually accessible by the reporter, and is not tagged with "apport-failed-retrace" or "apport-request-retrace"; otherwise file it anyway.
1053
1054
        try:
3587 by Benjamin Drung
launchpad: Fix missing close() on urlopen
1055
            with urllib.request.urlopen(url + "/+text") as f:
1056
                line = f.readline()
1057
                if not line.startswith(b"bug:"):
1058
                    # presumably a 404 etc. page,
1059
                    # which happens for private bugs
1060
                    return True
1061
1062
                # check tags
1063
                for line in f:
1064
                    if line.startswith(b"tags:"):
1065
                        if (
1066
                            b"apport-failed-retrace" in line
1067
                            or b"apport-request-retrace" in line
1068
                        ):
1069
                            return None
1070
                        else:
1071
                            break
1072
1073
                    # stop at the first task, tags are in the first block
1074
                    if not line.strip():
1075
                        break
3553 by Benjamin Drung
Rename IOError to OSError
1076
        except OSError:
2098 by Martin Pitt
launchpad.py: Override known() to check if the master bug is actually accessible by the reporter, and is not tagged with "apport-failed-retrace" or "apport-request-retrace"; otherwise file it anyway.
1077
            # if we are offline, or LP is down, upload will fail anyway, so we
1078
            # can just as well avoid the upload
1079
            return url
1080
1081
        return url
1082
887 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement private crash bug handling,
1083
    def _subscribe_triaging_team(self, bug, report):
3510 by Benjamin Drung
Format Python code with black
1084
        """Subscribe the right triaging team to the bug."""
887 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement private crash bug handling,
1085
2811 by Martin Pitt
* Adjust code to match latest pep8 checker.
1086
        # FIXME: this entire function is an ugly Ubuntu specific hack until LP
1087
        # gets a real crash db; see https://wiki.ubuntu.com/CrashReporting
887 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement private crash bug handling,
1088
3510 by Benjamin Drung
Format Python code with black
1089
        if (
1090
            "DistroRelease" in report
1091
            and report["DistroRelease"].split()[0] != "Ubuntu"
1092
        ):
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
1093
            return  # only Ubuntu bugs are filed private
2189 by Martin Pitt
remove trailing whitespace
1094
2811 by Martin Pitt
* Adjust code to match latest pep8 checker.
1095
        # use a url hack here, it is faster
3632 by Benjamin Drung
Ignore remaining protected-access pylint complaints
1096
        # pylint: disable=protected-access
3510 by Benjamin Drung
Format Python code with black
1097
        person = "%s~%s" % (
1098
            self.launchpad._root_uri,
1099
            self.options.get("triaging_team", "ubuntu-crashes-universe"),
1100
        )
1101
        if not person.replace(str(self.launchpad._root_uri), "").strip(
1102
            "~"
3561 by Benjamin Drung
Fix pylint's use-maxsplit-arg
1103
        ) in [
1104
            str(sub).split("/", maxsplit=1)[-1] for sub in bug.subscriptions
1105
        ]:
3135 by Brian Murray
apport/crashdb_impl/launchpad.py: only subscribe the person if they are not already subscribed - seems to resolve a crash
1106
            bug.subscribe(person=person)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1107
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1108
    def _generate_upload_blob(self, report):
3510 by Benjamin Drung
Format Python code with black
1109
        """Generate a multipart/MIME temporary file for uploading.
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1110
1111
        You have to close the returned file object after you are done with it.
3510 by Benjamin Drung
Format Python code with black
1112
        """
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1113
        # set reprocessing tags
1114
        hdr = {}
3510 by Benjamin Drung
Format Python code with black
1115
        hdr["Tags"] = "apport-%s" % report["ProblemType"].lower()
1116
        a = report.get("PackageArchitecture")
1117
        if not a or a == "all":
1118
            a = report.get("Architecture")
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1119
        if a:
3510 by Benjamin Drung
Format Python code with black
1120
            hdr["Tags"] += " " + a
1121
        if "Tags" in report:
1122
            hdr["Tags"] += " " + self._filter_tag_names(report["Tags"])
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1123
1124
        # privacy/retracing for distro reports
1125
        # FIXME: ugly hack until LP has a real crash db
3510 by Benjamin Drung
Format Python code with black
1126
        if "DistroRelease" in report:
1127
            if a and (
1128
                "VmCore" in report
1129
                or "CoreDump" in report
1130
                or "LaunchpadPrivate" in report
1131
            ):
1132
                hdr["Private"] = "yes"
1133
                hdr["Subscribers"] = report.get(
1134
                    "LaunchpadSubscribe",
1135
                    self.options.get("initial_subscriber", "apport"),
1136
                )
1137
                hdr["Tags"] += " need-%s-retrace" % a
1138
            elif "Traceback" in report:
1139
                hdr["Private"] = "yes"
1140
                hdr["Subscribers"] = "apport"
1141
                hdr["Tags"] += " need-duplicate-check"
1142
        if (
1143
            "DuplicateSignature" in report
1144
            and "need-duplicate-check" not in hdr["Tags"]
1145
        ):
1146
            hdr["Tags"] += " need-duplicate-check"
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1147
1148
        # if we have checkbox submission key, link it to the bug; keep text
1149
        # reference until the link is shown in Launchpad's UI
3510 by Benjamin Drung
Format Python code with black
1150
        if "CheckboxSubmission" in report:
1151
            hdr["HWDB-Submission"] = report["CheckboxSubmission"]
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1152
1153
        # order in which keys should appear in the temporary file
3510 by Benjamin Drung
Format Python code with black
1154
        order = [
1155
            "ProblemType",
1156
            "DistroRelease",
1157
            "Package",
1158
            "Regression",
1159
            "Reproducible",
1160
            "TestedUpstream",
1161
            "ProcVersionSignature",
1162
            "Uname",
1163
            "NonfreeKernelModules",
1164
        ]
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1165
1166
        # write MIME/Multipart version into temporary file
3595 by Benjamin Drung
Fix pylints consider-using-with
1167
        # temporary file is returned, pylint: disable=consider-using-with
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1168
        mime = tempfile.TemporaryFile()
3510 by Benjamin Drung
Format Python code with black
1169
        report.write_mime(
1170
            mime,
1171
            extra_headers=hdr,
1172
            skip_keys=["Tags", "LaunchpadPrivate", "LaunchpadSubscribe"],
1173
            priority_fields=order,
1174
        )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1175
        mime.flush()
1176
        mime.seek(0)
1177
1178
        return mime
1179
2487 by Martin Pitt
* launchpad.py: Replace characters from tags which are not allowed by Launchpad with '.' (LP: #1029479)
1180
    @classmethod
1181
    def _filter_tag_names(klass, tags):
3510 by Benjamin Drung
Format Python code with black
1182
        """Replace characters from tags which are not palatable to Launchpad"""
2487 by Martin Pitt
* launchpad.py: Replace characters from tags which are not allowed by Launchpad with '.' (LP: #1029479)
1183
3510 by Benjamin Drung
Format Python code with black
1184
        res = ""
1185
        for ch in tags.lower().encode("ASCII", errors="ignore"):
1186
            if ch in b"abcdefghijklmnopqrstuvwxyz0123456789 " or (
1187
                len(res) > 0 and ch in b"+-."
1188
            ):
3383 by Benjamin Drung
Drop Python 2 support
1189
                res += chr(ch)
2487 by Martin Pitt
* launchpad.py: Replace characters from tags which are not allowed by Launchpad with '.' (LP: #1029479)
1190
            else:
3510 by Benjamin Drung
Format Python code with black
1191
                res += "."
2487 by Martin Pitt
* launchpad.py: Replace characters from tags which are not allowed by Launchpad with '.' (LP: #1029479)
1192
1193
        return res
1194
3110 by Martin Pitt
Fix PEP-8 errors with latest pycodestyle
1195
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1196
#
1197
# Launchpad storeblob API (should go into launchpadlib, see LP #315358)
1198
#
1199
1200
_https_upload_callback = None
1201
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
1202
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1203
#
1204
# This progress code is based on KodakLoader by Jason Hildebrand
1205
# <jason@opensky.ca>. See http://www.opensky.ca/~jdhildeb/software/kodakloader/
1206
# for details.
3536 by Benjamin Drung
Do not use from-imports for standard libraries
1207
class HTTPSProgressConnection(http.client.HTTPSConnection):
3510 by Benjamin Drung
Format Python code with black
1208
    """Implement a HTTPSConnection with an optional callback function for
1209
    upload progress."""
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1210
1211
    def send(self, data):
1212
        # if callback has not been set, call the old method
1213
        if not _https_upload_callback:
3536 by Benjamin Drung
Do not use from-imports for standard libraries
1214
            http.client.HTTPSConnection.send(self, data)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1215
            return
1216
1217
        sent = 0
1218
        total = len(data)
1219
        chunksize = 1024
1220
        while sent < total:
1221
            _https_upload_callback(sent, total)
1222
            t1 = time.time()
3536 by Benjamin Drung
Do not use from-imports for standard libraries
1223
            http.client.HTTPSConnection.send(
1224
                self, data[sent : (sent + chunksize)]
1225
            )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1226
            sent += chunksize
1227
            t2 = time.time()
1228
2189 by Martin Pitt
remove trailing whitespace
1229
            # adjust chunksize so that it takes between .5 and 2
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1230
            # seconds to send a chunk
2422 by Martin Pitt
* launchpad.py: Ensure that upload chunk size does not underrun. (LP: #1013334)
1231
            if chunksize > 1024:
3510 by Benjamin Drung
Format Python code with black
1232
                if t2 - t1 < 0.5:
2422 by Martin Pitt
* launchpad.py: Ensure that upload chunk size does not underrun. (LP: #1013334)
1233
                    chunksize <<= 1
1234
                elif t2 - t1 > 2:
1235
                    chunksize >>= 1
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1236
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
1237
3536 by Benjamin Drung
Do not use from-imports for standard libraries
1238
class HTTPSProgressHandler(urllib.request.HTTPSHandler):
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1239
    def https_open(self, req):
1240
        return self.do_open(HTTPSProgressConnection, req)
1241
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
1242
3510 by Benjamin Drung
Format Python code with black
1243
def upload_blob(blob, progress_callback=None, hostname="launchpad.net"):
1244
    """Upload blob (file-like object) to Launchpad.
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1245
1246
    progress_callback can be set to a function(sent, total) which is regularly
1247
    called with the number of bytes already sent and total number of bytes to
1248
    send. It is called every 0.5 to 2 seconds (dynamically adapted to upload
1249
    bandwidth).
1250
1251
    Return None on error, or the ticket number on success.
1252
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
1253
    By default this uses the production Launchpad hostname. Set
3580 by Benjamin Drung
Add support for qastaging.launchpad.net
1254
    hostname to 'launchpad.dev', 'qastaging.launchpad.net', or
1255
    'staging.launchpad.net' to use another instance for testing.
3510 by Benjamin Drung
Format Python code with black
1256
    """
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1257
    ticket = None
3510 by Benjamin Drung
Format Python code with black
1258
    url = "https://%s/+storeblob" % hostname
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1259
3615 by Benjamin Drung
Avoid global variables (where possible)
1260
    global _https_upload_callback  # pylint: disable=global-statement
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1261
    _https_upload_callback = progress_callback
1262
2347 by Martin Pitt
* launchpad.py: Drop the external multipartpost_handler.py (which is not portable to Python 3) and replace it with using the standard email module.
1263
    # build the form-data multipart/MIME request
1264
    data = email.mime.multipart.MIMEMultipart()
1265
3510 by Benjamin Drung
Format Python code with black
1266
    submit = email.mime.text.MIMEText("1")
1267
    submit.add_header("Content-Disposition", 'form-data; name="FORM_SUBMIT"')
2347 by Martin Pitt
* launchpad.py: Drop the external multipartpost_handler.py (which is not portable to Python 3) and replace it with using the standard email module.
1268
    data.attach(submit)
1269
3510 by Benjamin Drung
Format Python code with black
1270
    form_blob = email.mime.base.MIMEBase("application", "octet-stream")
1271
    form_blob.add_header(
1272
        "Content-Disposition", 'form-data; name="field.blob"; filename="x"'
1273
    )
1274
    form_blob.set_payload(blob.read().decode("ascii"))
2347 by Martin Pitt
* launchpad.py: Drop the external multipartpost_handler.py (which is not portable to Python 3) and replace it with using the standard email module.
1275
    data.attach(form_blob)
1276
3536 by Benjamin Drung
Do not use from-imports for standard libraries
1277
    data_flat = io.BytesIO()
3383 by Benjamin Drung
Drop Python 2 support
1278
    gen = email.generator.BytesGenerator(data_flat, mangle_from_=False)
2349 by Martin Pitt
* launchpad.py: Also work with Python 3. Deal gracefully with a missing "launchpadlib" module; this is not yet available for Python 3, but not required for client-side reporting.
1279
    gen.flatten(data)
2347 by Martin Pitt
* launchpad.py: Drop the external multipartpost_handler.py (which is not portable to Python 3) and replace it with using the standard email module.
1280
1281
    # do the request; we need to explicitly set the content type here, as it
1282
    # defaults to x-www-form-urlencoded
3536 by Benjamin Drung
Do not use from-imports for standard libraries
1283
    req = urllib.request.Request(url, data_flat.getvalue())
3510 by Benjamin Drung
Format Python code with black
1284
    req.add_header(
1285
        "Content-Type", "multipart/form-data; boundary=" + data.get_boundary()
1286
    )
3536 by Benjamin Drung
Do not use from-imports for standard libraries
1287
    opener = urllib.request.build_opener(HTTPSProgressHandler)
2347 by Martin Pitt
* launchpad.py: Drop the external multipartpost_handler.py (which is not portable to Python 3) and replace it with using the standard email module.
1288
    result = opener.open(req)
3510 by Benjamin Drung
Format Python code with black
1289
    ticket = result.info().get("X-Launchpad-Blob-Token")
2349 by Martin Pitt
* launchpad.py: Also work with Python 3. Deal gracefully with a missing "launchpadlib" module; this is not yet available for Python 3, but not required for client-side reporting.
1290
2347 by Martin Pitt
* launchpad.py: Drop the external multipartpost_handler.py (which is not portable to Python 3) and replace it with using the standard email module.
1291
    assert ticket
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1292
    return ticket
887 by Martin Pitt
* apport/crashdb_impl/launchpad.py: Implement private crash bug handling,
1293
3110 by Martin Pitt
Fix PEP-8 errors with latest pycodestyle
1294
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1295
#
1296
# Unit tests
1297
#
1298
3510 by Benjamin Drung
Format Python code with black
1299
if __name__ == "__main__":
3497 by Benjamin Drung
Sort Python imports with isort
1300
    import subprocess
1301
    import unittest
3036 by Martin Pitt
* Tests: Move to unittest's builtin "mock" module.
1302
    from unittest.mock import patch
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1303
3615 by Benjamin Drung
Avoid global variables (where possible)
1304
    _CACHE = {}
1305
1306
    def cache(func):
1307
        """Decorator to cache result of function/method call.
1308
1309
        The cache is ignored if force_fresh is set to True.
1310
        """
1311
1312
        def try_to_get_from_cache(*args, **kwargs):
1313
            if kwargs.get("force_fresh", False):
1314
                return func(*args, **kwargs)
1315
            if func.__name__ not in _CACHE:
1316
                _CACHE[func.__name__] = func(*args, **kwargs)
1317
            return _CACHE[func.__name__]
1318
1319
        return try_to_get_from_cache
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1320
1680 by Martin Pitt
Rename all TestCase classes to "_T", which makes it much easier to run individual tests from the command line.
1321
    class _T(unittest.TestCase):
3632 by Benjamin Drung
Ignore remaining protected-access pylint complaints
1322
        # pylint: disable=protected-access
1323
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1324
        # this assumes that a source package 'coreutils' exists and builds a
1325
        # binary package 'coreutils'
3510 by Benjamin Drung
Format Python code with black
1326
        test_package = "coreutils"
1327
        test_srcpackage = "coreutils"
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1328
1329
        #
1330
        # Generic tests, should work for all CrashDB implementations
1331
        #
1332
1333
        def setUp(self):
3615 by Benjamin Drung
Avoid global variables (where possible)
1334
            self.crashdb = self._get_instance()
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1335
1336
            # create a local reference report so that we can compare
1337
            # DistroRelease, Architecture, etc.
1338
            self.ref_report = apport.Report()
1339
            self.ref_report.add_os_info()
1340
            self.ref_report.add_user_info()
3510 by Benjamin Drung
Format Python code with black
1341
            self.ref_report["SourcePackage"] = "coreutils"
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
1342
1343
            # Objects tests rely on.
3510 by Benjamin Drung
Format Python code with black
1344
            self._create_project("langpack-o-matic")
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
1345
1346
        def _create_project(self, name):
3510 by Benjamin Drung
Format Python code with black
1347
            """Create a project using launchpadlib to be used by tests."""
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
1348
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
1349
            project = self.crashdb.launchpad.projects[name]
1350
            if not project:
1351
                self.crashdb.launchpad.projects.new_project(
3510 by Benjamin Drung
Format Python code with black
1352
                    description=name + "description",
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
1353
                    display_name=name,
1354
                    name=name,
3510 by Benjamin Drung
Format Python code with black
1355
                    summary=name + "summary",
1356
                    title=name + "title",
1357
                )
1785.3.2 by Diogo Matsubara
change launchpad crashdb implemenetation to use dev instance or staging
1358
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
1359
        @property
1360
        def hostname(self):
3510 by Benjamin Drung
Format Python code with black
1361
            """Get the Launchpad hostname for the given crashdb."""
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
1362
1785.3.3 by Diogo Matsubara
add a method to return the hostname according to the LP instance being used and fix the way attachments are uploaded when filing bugs for testing
1363
            return self.crashdb.get_hostname()
1364
3615 by Benjamin Drung
Avoid global variables (where possible)
1365
        @cache
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1366
        def get_segv_report(self, force_fresh=False):
3638 by Benjamin Drung
Fix pylints unused-argument
1367
            # force_fresh used by @cache, pylint: disable=unused-argument
3510 by Benjamin Drung
Format Python code with black
1368
            """Generate SEGV crash report.
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1369
1370
            This is only done once, subsequent calls will return the already
1371
            existing ID, unless force_fresh is True.
1372
1373
            Return the ID.
3510 by Benjamin Drung
Format Python code with black
1374
            """
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
1375
            r = self._generate_sigsegv_report()
1337 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add test case: Update a bug report which
1376
            r.add_package_info(self.test_package)
1377
            r.add_os_info()
1378
            r.add_gdb_info()
1379
            r.add_user_info()
3510 by Benjamin Drung
Format Python code with black
1380
            self.assertEqual(
1381
                r.standard_title(), "crash crashed with SIGSEGV in f()"
1382
            )
1337 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add test case: Update a bug report which
1383
1648 by Martin Pitt
launchpad.py: Ensure that text attachments on initial bug filing are valid UTF-8. (LP: #453203)
1384
            # add some binary gibberish which isn't UTF-8
3510 by Benjamin Drung
Format Python code with black
1385
            r["ShortGibberish"] = ' "]\xb6"\n'
1386
            r["LongGibberish"] = "a\nb\nc\nd\ne\n\xff\xff\xff\n\f"
1648 by Martin Pitt
launchpad.py: Ensure that text attachments on initial bug filing are valid UTF-8. (LP: #453203)
1387
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1388
            # create a bug for the report
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
1389
            bug_target = self._get_bug_target(self.crashdb, r)
1828 by Martin Pitt
replace obsolete TestCase.assert_() with TestCase.assertTrue()
1390
            self.assertTrue(bug_target)
1337 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add test case: Update a bug report which
1391
3625 by Benjamin Drung
Fix pylints redefined-builtin
1392
            crash_id = self._file_bug(bug_target, r)
1393
            self.assertTrue(crash_id > 0)
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1394
3510 by Benjamin Drung
Format Python code with black
1395
            sys.stderr.write(
1396
                "(Created SEGV report: https://%s/bugs/%i) "
3625 by Benjamin Drung
Fix pylints redefined-builtin
1397
                % (self.hostname, crash_id)
3510 by Benjamin Drung
Format Python code with black
1398
            )
3625 by Benjamin Drung
Fix pylints redefined-builtin
1399
            return crash_id
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1400
3615 by Benjamin Drung
Avoid global variables (where possible)
1401
        @cache
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1402
        def get_python_report(self):
3510 by Benjamin Drung
Format Python code with black
1403
            """Generate Python crash report.
2416 by Martin Pitt
launchpad.py: PEP8 fixes
1404
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1405
            Return the ID.
3510 by Benjamin Drung
Format Python code with black
1406
            """
1407
            r = apport.Report("Crash")
1408
            r["ExecutablePath"] = "/bin/foo"
1409
            r[
1410
                "Traceback"
1411
            ] = """Traceback (most recent call last):
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1412
  File "/bin/foo", line 67, in fuzz
1827 by Martin Pitt
Python 3 compatible "print" (not everything yet)
1413
    print(weird)
3510 by Benjamin Drung
Format Python code with black
1414
NameError: global name 'weird' is not defined"""
1415
            r["Tags"] = "boogus pybogus"
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1416
            r.add_package_info(self.test_package)
1417
            r.add_os_info()
1418
            r.add_user_info()
3510 by Benjamin Drung
Format Python code with black
1419
            self.assertEqual(
1420
                r.standard_title(),
1421
                "foo crashed with NameError in fuzz():"
1422
                " global name 'weird' is not defined",
1423
            )
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1424
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
1425
            bug_target = self._get_bug_target(self.crashdb, r)
1828 by Martin Pitt
replace obsolete TestCase.assert_() with TestCase.assertTrue()
1426
            self.assertTrue(bug_target)
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1427
3625 by Benjamin Drung
Fix pylints redefined-builtin
1428
            crash_id = self._file_bug(bug_target, r)
1429
            self.assertTrue(crash_id > 0)
3510 by Benjamin Drung
Format Python code with black
1430
            sys.stderr.write(
1431
                "(Created Python report: https://%s/bugs/%i) "
3625 by Benjamin Drung
Fix pylints redefined-builtin
1432
                % (self.hostname, crash_id)
3510 by Benjamin Drung
Format Python code with black
1433
            )
3625 by Benjamin Drung
Fix pylints redefined-builtin
1434
            return crash_id
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1435
3615 by Benjamin Drung
Avoid global variables (where possible)
1436
        @cache
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1437
        def get_uncommon_description_report(self, force_fresh=False):
3638 by Benjamin Drung
Fix pylints unused-argument
1438
            # force_fresh used by @cache, pylint: disable=unused-argument
3510 by Benjamin Drung
Format Python code with black
1439
            """File a bug report with an uncommon description.
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1440
1441
            This is only done once, subsequent calls will return the already
1442
            existing ID, unless force_fresh is True.
1443
1444
            Example taken from real LP bug 269539. It contains only
1445
            ProblemType/Architecture/DistroRelease in the description, and has
1446
            free-form description text after the Apport data.
1447
1448
            Return the ID.
3510 by Benjamin Drung
Format Python code with black
1449
            """
1450
            desc = """problem
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1451
1452
ProblemType: Package
1453
Architecture: amd64
1454
DistroRelease: Ubuntu 8.10
1455
1456
more text
1457
1458
and more
3510 by Benjamin Drung
Format Python code with black
1459
"""
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1460
            bug = self.crashdb.launchpad.bugs.createBug(
3510 by Benjamin Drung
Format Python code with black
1461
                title="mixed description bug",
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1462
                description=desc,
3510 by Benjamin Drung
Format Python code with black
1463
                target=self.crashdb.lp_distro,
1464
            )
1465
            sys.stderr.write(
1466
                "(Created uncommon description: https://%s/bugs/%i) "
1467
                % (self.hostname, bug.id)
1468
            )
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1469
1470
            return bug.id
1471
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1472
        def test_1_download(self):
3510 by Benjamin Drung
Format Python code with black
1473
            """download()"""
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1474
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1475
            r = self.crashdb.download(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1476
            self.assertEqual(r["ProblemType"], "Crash")
1477
            self.assertEqual(r["Title"], "crash crashed with SIGSEGV in f()")
1478
            self.assertEqual(
1479
                r["DistroRelease"], self.ref_report["DistroRelease"]
1480
            )
1481
            self.assertEqual(
1482
                r["Architecture"], self.ref_report["Architecture"]
1483
            )
1484
            self.assertEqual(r["Uname"], self.ref_report["Uname"])
1485
            self.assertEqual(
1486
                r.get("NonfreeKernelModules"),
1487
                self.ref_report.get("NonfreeKernelModules"),
1488
            )
1489
            self.assertEqual(
1490
                r.get("UserGroups"), self.ref_report.get("UserGroups")
1491
            )
1492
            tags = set(r["Tags"].split())
1493
            self.assertEqual(
1494
                tags,
1495
                set(
1496
                    [
1497
                        self.crashdb.arch_tag,
1498
                        "apport-crash",
1499
                        apport.packaging.get_system_architecture(),
1500
                    ]
1501
                ),
1502
            )
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1503
3510 by Benjamin Drung
Format Python code with black
1504
            self.assertEqual(r["Signal"], "11")
1505
            self.assertTrue(r["ExecutablePath"].endswith("/crash"))
1506
            self.assertEqual(r["SourcePackage"], self.test_srcpackage)
1507
            self.assertTrue(r["Package"].startswith(self.test_package + " "))
1508
            self.assertIn("f (x=42)", r["Stacktrace"])
1509
            self.assertIn("f (x=42)", r["StacktraceTop"])
1510
            self.assertIn("f (x=42)", r["ThreadStacktrace"])
1511
            self.assertGreater(len(r["CoreDump"]), 1000)
1512
            self.assertIn("Dependencies", r)
1513
            self.assertIn("Disassembly", r)
1514
            self.assertIn("Registers", r)
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1515
1696 by Martin Pitt
launchpad.py: Do not put the Tags: field into the bug description, since they
1516
            # check tags
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1517
            r = self.crashdb.download(self.get_python_report())
3510 by Benjamin Drung
Format Python code with black
1518
            tags = set(r["Tags"].split())
1519
            self.assertEqual(
1520
                tags,
1521
                set(
1522
                    [
1523
                        "apport-crash",
1524
                        "boogus",
1525
                        "pybogus",
1526
                        "need-duplicate-check",
1527
                        apport.packaging.get_system_architecture(),
1528
                    ]
1529
                ),
1530
            )
1696 by Martin Pitt
launchpad.py: Do not put the Tags: field into the bug description, since they
1531
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1532
        def test_2_update_traces(self):
3510 by Benjamin Drung
Format Python code with black
1533
            """update_traces()"""
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1534
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1535
            r = self.crashdb.download(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1536
            self.assertIn("CoreDump", r)
1537
            self.assertIn("Dependencies", r)
1538
            self.assertIn("Disassembly", r)
1539
            self.assertIn("Registers", r)
1540
            self.assertIn("Stacktrace", r)
1541
            self.assertIn("ThreadStacktrace", r)
1542
            self.assertEqual(r["Title"], "crash crashed with SIGSEGV in f()")
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1543
2117 by Martin Pitt
apport-retrace: Fix "an useful" typo. (LP: #911437)
1544
            # updating with a useless stack trace retains core dump
3510 by Benjamin Drung
Format Python code with black
1545
            r["StacktraceTop"] = "?? ()"
1546
            r["Stacktrace"] = "long\ntrace"
1547
            r["ThreadStacktrace"] = "thread\neven longer\ntrace"
1548
            r["FooBar"] = "bogus"
1549
            self.crashdb.update_traces(
1550
                self.get_segv_report(), r, "I can has a better retrace?"
1551
            )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1552
            r = self.crashdb.download(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1553
            self.assertIn("CoreDump", r)
1554
            self.assertIn("Dependencies", r)
1555
            self.assertIn("Disassembly", r)
1556
            self.assertIn("Registers", r)
1557
            self.assertIn(
1558
                "Stacktrace", r
1559
            )  # TODO: ascertain that it's the updated one
1560
            self.assertIn("ThreadStacktrace", r)
1561
            self.assertNotIn("FooBar", r)
1562
            self.assertEqual(r["Title"], "crash crashed with SIGSEGV in f()")
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1563
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1564
            tags = self.crashdb.launchpad.bugs[self.get_segv_report()].tags
3510 by Benjamin Drung
Format Python code with black
1565
            self.assertIn("apport-crash", tags)
1566
            self.assertNotIn("apport-collected", tags)
1665 by Martin Pitt
launchpad.py, update(): Add apport-collect for full update
1567
2117 by Martin Pitt
apport-retrace: Fix "an useful" typo. (LP: #911437)
1568
            # updating with a useful stack trace removes core dump
3510 by Benjamin Drung
Format Python code with black
1569
            r["StacktraceTop"] = (
1570
                "read () from /lib/libc.6.so\nfoo (i=1)"
1571
                " from /usr/lib/libfoo.so"
1572
            )
1573
            r["Stacktrace"] = "long\ntrace"
1574
            r["ThreadStacktrace"] = "thread\neven longer\ntrace"
1575
            self.crashdb.update_traces(
1576
                self.get_segv_report(), r, "good retrace!"
1577
            )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1578
            r = self.crashdb.download(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1579
            self.assertNotIn("CoreDump", r)
1580
            self.assertIn("Dependencies", r)
1581
            self.assertIn("Disassembly", r)
1582
            self.assertIn("Registers", r)
1583
            self.assertIn("Stacktrace", r)
1584
            self.assertIn("ThreadStacktrace", r)
1585
            self.assertNotIn("FooBar", r)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1586
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
1587
            # as previous title had standard form, the top function gets
1588
            # updated
3510 by Benjamin Drung
Format Python code with black
1589
            self.assertEqual(
1590
                r["Title"], "crash crashed with SIGSEGV in read()"
1591
            )
2189 by Martin Pitt
remove trailing whitespace
1592
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
1593
            # respects title amendments
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1594
            bug = self.crashdb.launchpad.bugs[self.get_segv_report()]
3510 by Benjamin Drung
Format Python code with black
1595
            bug.title = "crash crashed with SIGSEGV in f() on exit"
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
1596
            try:
1597
                bug.lp_save()
1598
            except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
1599
                pass  # LP#336866 workaround
3510 by Benjamin Drung
Format Python code with black
1600
            r["StacktraceTop"] = (
1601
                "read () from /lib/libc.6.so\nfoo (i=1)"
1602
                " from /usr/lib/libfoo.so"
1603
            )
1604
            self.crashdb.update_traces(
1605
                self.get_segv_report(), r, "good retrace with title amendment"
1606
            )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1607
            r = self.crashdb.download(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1608
            self.assertEqual(
1609
                r["Title"], "crash crashed with SIGSEGV in read() on exit"
1610
            )
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
1611
1612
            # does not destroy custom titles
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1613
            bug = self.crashdb.launchpad.bugs[self.get_segv_report()]
3510 by Benjamin Drung
Format Python code with black
1614
            bug.title = "crash is crashy"
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
1615
            try:
1616
                bug.lp_save()
1617
            except HTTPError:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
1618
                pass  # LP#336866 workaround
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
1619
3510 by Benjamin Drung
Format Python code with black
1620
            r["StacktraceTop"] = (
1621
                "read () from /lib/libc.6.so\nfoo (i=1)"
1622
                " from /usr/lib/libfoo.so"
1623
            )
1624
            self.crashdb.update_traces(
1625
                self.get_segv_report(), r, "good retrace with custom title"
1626
            )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1627
            r = self.crashdb.download(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1628
            self.assertEqual(r["Title"], "crash is crashy")
2071 by Martin Pitt
launchpad.net: When sending retraced results back to the bug report, update the topmost function in the bug title. (LP: #869970)
1629
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1630
            # test various situations which caused crashes
3510 by Benjamin Drung
Format Python code with black
1631
            r["Stacktrace"] = ""  # empty file
1632
            r[
1633
                "ThreadStacktrace"
1634
            ] = '"]\xb6"\n'  # not interpretable as UTF-8, LP #353805
1635
            r["StacktraceSource"] = "a\nb\nc\nd\ne\n\xff\xff\xff\n\f"
1636
            self.crashdb.update_traces(self.get_segv_report(), r, "tests")
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1637
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1638
        def test_get_comment_url(self):
3510 by Benjamin Drung
Format Python code with black
1639
            """get_comment_url() for non-ASCII titles"""
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1640
3510 by Benjamin Drung
Format Python code with black
1641
            title = b"1\xc3\xa4\xe2\x99\xa52"
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1642
1643
            # distro, UTF-8 bytestring
3510 by Benjamin Drung
Format Python code with black
1644
            r = apport.Report("Bug")
1645
            r["Title"] = title
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1646
            url = self.crashdb.get_comment_url(r, 42)
3510 by Benjamin Drung
Format Python code with black
1647
            self.assertTrue(
1648
                url.endswith(
1649
                    "/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52"
1650
                )
1651
            )
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1652
1653
            # distro, unicode
3510 by Benjamin Drung
Format Python code with black
1654
            r["Title"] = title.decode("UTF-8")
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1655
            url = self.crashdb.get_comment_url(r, 42)
3510 by Benjamin Drung
Format Python code with black
1656
            self.assertTrue(
1657
                url.endswith(
1658
                    "/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52"
1659
                )
1660
            )
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1661
1662
            # package, unicode
3510 by Benjamin Drung
Format Python code with black
1663
            r["SourcePackage"] = "coreutils"
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1664
            url = self.crashdb.get_comment_url(r, 42)
3510 by Benjamin Drung
Format Python code with black
1665
            self.assertTrue(
1666
                url.endswith(
1667
                    "/ubuntu/+source/coreutils/+filebug/42"
1668
                    "?field.title=1%C3%A4%E2%99%A52"
1669
                )
1670
            )
2167 by Martin Pitt
launchpad.py: Fix crash on unicode report titles. (LP: #896626)
1671
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1672
        def test_update_description(self):
3510 by Benjamin Drung
Format Python code with black
1673
            """update() with changing description"""
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1674
3510 by Benjamin Drung
Format Python code with black
1675
            bug_target = self.crashdb.lp_distro.getSourcePackage(name="bash")
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
1676
            bug = self.crashdb.launchpad.bugs.createBug(
3510 by Benjamin Drung
Format Python code with black
1677
                description="test description for test bug.",
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
1678
                target=bug_target,
3510 by Benjamin Drung
Format Python code with black
1679
                title="testbug",
1680
            )
3625 by Benjamin Drung
Fix pylints redefined-builtin
1681
            crash_id = bug.id
1682
            self.assertTrue(crash_id > 0)
1683
            sys.stderr.write(f"(https://{self.hostname}/bugs/{crash_id}) ")
3510 by Benjamin Drung
Format Python code with black
1684
1685
            r = apport.Report("Bug")
1686
1687
            r["OneLiner"] = b"bogus\xe2\x86\x92".decode("UTF-8")
1688
            r["StacktraceTop"] = "f()\ng()\nh(1)"
1689
            r["ShortGoo"] = "lineone\nlinetwo"
1690
            r["DpkgTerminalLog"] = "one\ntwo\nthree\nfour\nfive\nsix"
1691
            r["VarLogDistupgradeBinGoo"] = b"\x01" * 1024
1692
3625 by Benjamin Drung
Fix pylints redefined-builtin
1693
            self.crashdb.update(crash_id, r, "NotMe", change_description=True)
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1694
3625 by Benjamin Drung
Fix pylints redefined-builtin
1695
            r = self.crashdb.download(crash_id)
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1696
3510 by Benjamin Drung
Format Python code with black
1697
            self.assertEqual(
1698
                r["OneLiner"], b"bogus\xe2\x86\x92".decode("UTF-8")
1699
            )
1700
            self.assertEqual(r["ShortGoo"], "lineone\nlinetwo")
1701
            self.assertEqual(
1702
                r["DpkgTerminalLog"], "one\ntwo\nthree\nfour\nfive\nsix"
1703
            )
1704
            self.assertEqual(r["VarLogDistupgradeBinGoo"], b"\x01" * 1024)
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1705
3510 by Benjamin Drung
Format Python code with black
1706
            self.assertEqual(
3625 by Benjamin Drung
Fix pylints redefined-builtin
1707
                self.crashdb.launchpad.bugs[crash_id].tags,
1708
                ["apport-collected"],
3510 by Benjamin Drung
Format Python code with black
1709
            )
1665 by Martin Pitt
launchpad.py, update(): Add apport-collect for full update
1710
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1711
        def test_update_comment(self):
3510 by Benjamin Drung
Format Python code with black
1712
            """update() with appending comment"""
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1713
3510 by Benjamin Drung
Format Python code with black
1714
            bug_target = self.crashdb.lp_distro.getSourcePackage(name="bash")
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1715
            # we need to fake an apport description separator here, since we
1716
            # want to be lazy and use download() for checking the result
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
1717
            bug = self.crashdb.launchpad.bugs.createBug(
3510 by Benjamin Drung
Format Python code with black
1718
                description="Pr0blem\n\n--- \nProblemType: Bug",
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
1719
                target=bug_target,
3510 by Benjamin Drung
Format Python code with black
1720
                title="testbug",
1721
            )
3625 by Benjamin Drung
Fix pylints redefined-builtin
1722
            crash_id = bug.id
1723
            self.assertTrue(crash_id > 0)
1724
            sys.stderr.write(f"(https://{self.hostname}/bugs/{crash_id}) ")
3510 by Benjamin Drung
Format Python code with black
1725
1726
            r = apport.Report("Bug")
1727
1728
            r["OneLiner"] = "bogus→"
1729
            r["StacktraceTop"] = "f()\ng()\nh(1)"
1730
            r["ShortGoo"] = "lineone\nlinetwo"
1731
            r["DpkgTerminalLog"] = "one\ntwo\nthree\nfour\nfive\nsix"
1732
            r["VarLogDistupgradeBinGoo"] = "\x01" * 1024
1733
3625 by Benjamin Drung
Fix pylints redefined-builtin
1734
            self.crashdb.update(crash_id, r, "meow", change_description=False)
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1735
3625 by Benjamin Drung
Fix pylints redefined-builtin
1736
            r = self.crashdb.download(crash_id)
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1737
3510 by Benjamin Drung
Format Python code with black
1738
            self.assertNotIn("OneLiner", r)
1739
            self.assertNotIn("ShortGoo", r)
1740
            self.assertEqual(r["ProblemType"], "Bug")
1741
            self.assertEqual(
1742
                r["DpkgTerminalLog"], "one\ntwo\nthree\nfour\nfive\nsix"
1743
            )
1744
            self.assertEqual(r["VarLogDistupgradeBinGoo"], "\x01" * 1024)
1658 by Martin Pitt
Add CrashDatabase.update() for adding all new fields of a report
1745
3510 by Benjamin Drung
Format Python code with black
1746
            self.assertEqual(
3625 by Benjamin Drung
Fix pylints redefined-builtin
1747
                self.crashdb.launchpad.bugs[crash_id].tags,
1748
                ["apport-collected"],
3510 by Benjamin Drung
Format Python code with black
1749
            )
1665 by Martin Pitt
launchpad.py, update(): Add apport-collect for full update
1750
1660 by Martin Pitt
add key filtering to to CrashDatabase.update()
1751
        def test_update_filter(self):
3510 by Benjamin Drung
Format Python code with black
1752
            """update() with a key filter"""
1660 by Martin Pitt
add key filtering to to CrashDatabase.update()
1753
3510 by Benjamin Drung
Format Python code with black
1754
            bug_target = self.crashdb.lp_distro.getSourcePackage(name="bash")
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
1755
            bug = self.crashdb.launchpad.bugs.createBug(
3510 by Benjamin Drung
Format Python code with black
1756
                description="test description for test bug",
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
1757
                target=bug_target,
3510 by Benjamin Drung
Format Python code with black
1758
                title="testbug",
1759
            )
3625 by Benjamin Drung
Fix pylints redefined-builtin
1760
            crash_id = bug.id
1761
            self.assertTrue(crash_id > 0)
1762
            sys.stderr.write(f"(https://{self.hostname}/bugs/{crash_id}) ")
3510 by Benjamin Drung
Format Python code with black
1763
1764
            r = apport.Report("Bug")
1765
1766
            r["OneLiner"] = "bogus→"
1767
            r["StacktraceTop"] = "f()\ng()\nh(1)"
1768
            r["ShortGoo"] = "lineone\nlinetwo"
1769
            r["DpkgTerminalLog"] = "one\ntwo\nthree\nfour\nfive\nsix"
1770
            r["VarLogDistupgradeBinGoo"] = "\x01" * 1024
1771
1772
            self.crashdb.update(
3625 by Benjamin Drung
Fix pylints redefined-builtin
1773
                crash_id,
3510 by Benjamin Drung
Format Python code with black
1774
                r,
1775
                "NotMe",
1776
                change_description=True,
1777
                key_filter=["ProblemType", "ShortGoo", "DpkgTerminalLog"],
1778
            )
1660 by Martin Pitt
add key filtering to to CrashDatabase.update()
1779
3625 by Benjamin Drung
Fix pylints redefined-builtin
1780
            r = self.crashdb.download(crash_id)
1660 by Martin Pitt
add key filtering to to CrashDatabase.update()
1781
3510 by Benjamin Drung
Format Python code with black
1782
            self.assertNotIn("OneLiner", r)
1783
            self.assertEqual(r["ShortGoo"], "lineone\nlinetwo")
1784
            self.assertEqual(r["ProblemType"], "Bug")
1785
            self.assertEqual(
1786
                r["DpkgTerminalLog"], "one\ntwo\nthree\nfour\nfive\nsix"
1787
            )
1788
            self.assertNotIn("VarLogDistupgradeBinGoo", r)
1660 by Martin Pitt
add key filtering to to CrashDatabase.update()
1789
3625 by Benjamin Drung
Fix pylints redefined-builtin
1790
            self.assertEqual(self.crashdb.launchpad.bugs[crash_id].tags, [])
1665 by Martin Pitt
launchpad.py, update(): Add apport-collect for full update
1791
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1792
        def test_get_distro_release(self):
3510 by Benjamin Drung
Format Python code with black
1793
            """get_distro_release()"""
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1794
3510 by Benjamin Drung
Format Python code with black
1795
            self.assertEqual(
1796
                self.crashdb.get_distro_release(self.get_segv_report()),
1797
                self.ref_report["DistroRelease"],
1798
            )
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
1799
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
1800
        def test_get_affected_packages(self):
3510 by Benjamin Drung
Format Python code with black
1801
            """get_affected_packages()"""
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
1802
3510 by Benjamin Drung
Format Python code with black
1803
            self.assertEqual(
1804
                self.crashdb.get_affected_packages(self.get_segv_report()),
1805
                [self.ref_report["SourcePackage"]],
1806
            )
1654 by Martin Pitt
New CrashDatabase API: get_affected_packages()
1807
1656 by Martin Pitt
New CrashDatabase API: is_reporter()
1808
        def test_is_reporter(self):
3510 by Benjamin Drung
Format Python code with black
1809
            """is_reporter()"""
1656 by Martin Pitt
New CrashDatabase API: is_reporter()
1810
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1811
            self.assertTrue(self.crashdb.is_reporter(self.get_segv_report()))
1861 by Martin Pitt
Convert deprecated failIf()/assert_() TestCase method calls to assertFalse()/assertTrue().
1812
            self.assertFalse(self.crashdb.is_reporter(1))
1656 by Martin Pitt
New CrashDatabase API: is_reporter()
1813
1655 by Martin Pitt
New CrashDatabase API: can_update()
1814
        def test_can_update(self):
3510 by Benjamin Drung
Format Python code with black
1815
            """can_update()"""
1655 by Martin Pitt
New CrashDatabase API: can_update()
1816
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1817
            self.assertTrue(self.crashdb.can_update(self.get_segv_report()))
1861 by Martin Pitt
Convert deprecated failIf()/assert_() TestCase method calls to assertFalse()/assertTrue().
1818
            self.assertFalse(self.crashdb.can_update(1))
1655 by Martin Pitt
New CrashDatabase API: can_update()
1819
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
1820
        def test_duplicates(self):
3510 by Benjamin Drung
Format Python code with black
1821
            """duplicate handling"""
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
1822
1823
            # initially we have no dups
3510 by Benjamin Drung
Format Python code with black
1824
            self.assertEqual(
1825
                self.crashdb.duplicate_of(self.get_segv_report()), None
1826
            )
1827
            self.assertEqual(
1828
                self.crashdb.get_fixed_version(self.get_segv_report()), None
1829
            )
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
1830
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1831
            segv_id = self.get_segv_report()
1832
            known_test_id = self.get_uncommon_description_report()
3510 by Benjamin Drung
Format Python code with black
1833
            known_test_id2 = self.get_uncommon_description_report(
1834
                force_fresh=True
1835
            )
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1836
1328 by Martin Pitt
launchpad.py test suite: fix typo in variable name
1837
            # dupe our segv_report and check that it worked; then undupe it
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1838
            r = self.crashdb.download(segv_id)
1839
            self.crashdb.close_duplicate(r, segv_id, known_test_id)
1840
            self.assertEqual(self.crashdb.duplicate_of(segv_id), known_test_id)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1841
1842
            # this should be a no-op
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1843
            self.crashdb.close_duplicate(r, segv_id, known_test_id)
1844
            self.assertEqual(self.crashdb.duplicate_of(segv_id), known_test_id)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1845
3510 by Benjamin Drung
Format Python code with black
1846
            self.assertEqual(
1847
                self.crashdb.get_fixed_version(segv_id), "invalid"
1848
            )
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1849
            self.crashdb.close_duplicate(r, segv_id, None)
1850
            self.assertEqual(self.crashdb.duplicate_of(segv_id), None)
1851
            self.assertEqual(self.crashdb.get_fixed_version(segv_id), None)
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
1852
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1853
            # this should have removed attachments; note that Stacktrace is
1854
            # short, and thus inline
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1855
            r = self.crashdb.download(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1856
            self.assertNotIn("CoreDump", r)
1857
            self.assertNotIn("Disassembly", r)
1858
            self.assertNotIn("ProcMaps", r)
1859
            self.assertNotIn("ProcStatus", r)
1860
            self.assertNotIn("Registers", r)
1861
            self.assertNotIn("ThreadStacktrace", r)
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
1862
1863
            # now try duplicating to a duplicate bug; this should automatically
1864
            # transition to the master bug
3510 by Benjamin Drung
Format Python code with black
1865
            self.crashdb.close_duplicate(
1866
                apport.Report(), known_test_id, known_test_id2
1867
            )
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1868
            self.crashdb.close_duplicate(r, segv_id, known_test_id)
3510 by Benjamin Drung
Format Python code with black
1869
            self.assertEqual(
1870
                self.crashdb.duplicate_of(segv_id), known_test_id2
1871
            )
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
1872
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1873
            self.crashdb.close_duplicate(apport.Report(), known_test_id, None)
1874
            self.crashdb.close_duplicate(apport.Report(), known_test_id2, None)
1875
            self.crashdb.close_duplicate(r, segv_id, None)
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1876
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1877
            # this should be a no-op
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1878
            self.crashdb.close_duplicate(apport.Report(), known_test_id, None)
1879
            self.assertEqual(self.crashdb.duplicate_of(known_test_id), None)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1880
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
1881
            self.crashdb.mark_regression(segv_id, known_test_id)
1882
            self._verify_marked_regression(segv_id)
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
1883
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1884
        def test_marking_segv(self):
3510 by Benjamin Drung
Format Python code with black
1885
            """processing status markings for signal crashes"""
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1886
1887
            # mark_retraced()
1888
            unretraced_before = self.crashdb.get_unretraced()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
1889
            self.assertIn(self.get_segv_report(), unretraced_before)
1890
            self.assertNotIn(self.get_python_report(), unretraced_before)
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1891
            self.crashdb.mark_retraced(self.get_segv_report())
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1892
            unretraced_after = self.crashdb.get_unretraced()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
1893
            self.assertNotIn(self.get_segv_report(), unretraced_after)
3510 by Benjamin Drung
Format Python code with black
1894
            self.assertEqual(
1895
                unretraced_before,
1896
                unretraced_after.union(set([self.get_segv_report()])),
1897
            )
1898
            self.assertEqual(
1899
                self.crashdb.get_fixed_version(self.get_segv_report()), None
1900
            )
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1901
1902
            # mark_retrace_failed()
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1903
            self._mark_needs_retrace(self.get_segv_report())
1904
            self.crashdb.mark_retraced(self.get_segv_report())
1905
            self.crashdb.mark_retrace_failed(self.get_segv_report())
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1906
            unretraced_after = self.crashdb.get_unretraced()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
1907
            self.assertNotIn(self.get_segv_report(), unretraced_after)
3510 by Benjamin Drung
Format Python code with black
1908
            self.assertEqual(
1909
                unretraced_before,
1910
                unretraced_after.union(set([self.get_segv_report()])),
1911
            )
1912
            self.assertEqual(
1913
                self.crashdb.get_fixed_version(self.get_segv_report()), None
1914
            )
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1915
1916
            # mark_retrace_failed() of invalid bug
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
1917
            self._mark_needs_retrace(self.get_segv_report())
1918
            self.crashdb.mark_retraced(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
1919
            self.crashdb.mark_retrace_failed(
1920
                self.get_segv_report(), "I don't like you"
1921
            )
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1922
            unretraced_after = self.crashdb.get_unretraced()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
1923
            self.assertNotIn(self.get_segv_report(), unretraced_after)
3510 by Benjamin Drung
Format Python code with black
1924
            self.assertEqual(
1925
                unretraced_before,
1926
                unretraced_after.union(set([self.get_segv_report()])),
1927
            )
1928
            self.assertEqual(
1929
                self.crashdb.get_fixed_version(self.get_segv_report()),
1930
                "invalid",
1931
            )
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
1932
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1933
        def test_marking_project(self):
3510 by Benjamin Drung
Format Python code with black
1934
            """processing status markings for a project CrashDB"""
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1935
1936
            # create a distro bug
1937
            distro_bug = self.crashdb.launchpad.bugs.createBug(
3510 by Benjamin Drung
Format Python code with black
1938
                description="foo",
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1939
                tags=self.crashdb.arch_tag,
1940
                target=self.crashdb.lp_distro,
3510 by Benjamin Drung
Format Python code with black
1941
                title="ubuntu distro retrace bug",
1942
            )
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1943
1944
            # create a project crash DB and a bug
3510 by Benjamin Drung
Format Python code with black
1945
            launchpad_instance = (
3580 by Benjamin Drung
Add support for qastaging.launchpad.net
1946
                os.environ.get("APPORT_LAUNCHPAD_INSTANCE") or "qastaging"
3510 by Benjamin Drung
Format Python code with black
1947
            )
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1948
1949
            project_db = CrashDatabase(
3510 by Benjamin Drung
Format Python code with black
1950
                os.environ.get("LP_CREDENTIALS"),
1951
                {
1952
                    "project": "langpack-o-matic",
1953
                    "launchpad_instance": launchpad_instance,
1954
                },
1955
            )
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1956
            project_bug = project_db.launchpad.bugs.createBug(
3510 by Benjamin Drung
Format Python code with black
1957
                description="bar",
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1958
                tags=project_db.arch_tag,
1959
                target=project_db.lp_distro,
3510 by Benjamin Drung
Format Python code with black
1960
                title="project retrace bug",
1961
            )
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1962
1963
            # on project_db, we recognize the project bug and can mark it
1964
            unretraced_before = project_db.get_unretraced()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
1965
            self.assertIn(project_bug.id, unretraced_before)
1966
            self.assertNotIn(distro_bug.id, unretraced_before)
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1967
            project_db.mark_retraced(project_bug.id)
1968
            unretraced_after = project_db.get_unretraced()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
1969
            self.assertNotIn(project_bug.id, unretraced_after)
3510 by Benjamin Drung
Format Python code with black
1970
            self.assertEqual(
1971
                unretraced_before,
1972
                unretraced_after.union(set([project_bug.id])),
1973
            )
1974
            self.assertEqual(
1975
                self.crashdb.get_fixed_version(project_bug.id), None
1976
            )
2410 by Martin Pitt
* launchpad.py: Recongize Launchpad projects for bug query and marking operations. (LP: #1003506)
1977
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
1978
        def test_marking_foreign_arch(self):
3510 by Benjamin Drung
Format Python code with black
1979
            """processing status markings for a project CrashDB"""
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
1980
1981
            # create a DB for fake arch
3510 by Benjamin Drung
Format Python code with black
1982
            launchpad_instance = (
3580 by Benjamin Drung
Add support for qastaging.launchpad.net
1983
                os.environ.get("APPORT_LAUNCHPAD_INSTANCE") or "qastaging"
3510 by Benjamin Drung
Format Python code with black
1984
            )
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
1985
            fakearch_db = CrashDatabase(
3510 by Benjamin Drung
Format Python code with black
1986
                os.environ.get("LP_CREDENTIALS"),
1987
                {
1988
                    "distro": "ubuntu",
1989
                    "launchpad_instance": launchpad_instance,
1990
                    "architecture": "fakearch",
1991
                },
1992
            )
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
1993
1994
            fakearch_unretraced_before = fakearch_db.get_unretraced()
1995
            systemarch_unretraced_before = self.crashdb.get_unretraced()
1996
1997
            # create a bug with a fake architecture
1998
            bug = self.crashdb.launchpad.bugs.createBug(
3510 by Benjamin Drung
Format Python code with black
1999
                description="foo",
2000
                tags=["need-fakearch-retrace"],
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
2001
                target=self.crashdb.lp_distro,
3510 by Benjamin Drung
Format Python code with black
2002
                title="ubuntu distro retrace bug for fakearch",
2003
            )
2004
            print(
2005
                "fake arch bug: https://staging.launchpad.net/bugs/%i" % bug.id
2006
            )
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
2007
2008
            fakearch_unretraced_after = fakearch_db.get_unretraced()
2009
            systemarch_unretraced_after = self.crashdb.get_unretraced()
2010
3510 by Benjamin Drung
Format Python code with black
2011
            self.assertEqual(
2012
                systemarch_unretraced_before, systemarch_unretraced_after
2013
            )
2014
            self.assertEqual(
2015
                fakearch_unretraced_after,
2016
                fakearch_unretraced_before.union(set([bug.id])),
2017
            )
2556 by Martin Pitt
* launchpad.py: Add "architecture" option to process reports for a foreign architecture.
2018
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
2019
        def test_marking_python(self):
3510 by Benjamin Drung
Format Python code with black
2020
            """processing status markings for interpreter crashes"""
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
2021
2022
            unchecked_before = self.crashdb.get_dup_unchecked()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
2023
            self.assertIn(self.get_python_report(), unchecked_before)
2024
            self.assertNotIn(self.get_segv_report(), unchecked_before)
3510 by Benjamin Drung
Format Python code with black
2025
            self.crashdb._mark_dup_checked(
2026
                self.get_python_report(), self.ref_report
2027
            )
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
2028
            unchecked_after = self.crashdb.get_dup_unchecked()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
2029
            self.assertNotIn(self.get_python_report(), unchecked_after)
3510 by Benjamin Drung
Format Python code with black
2030
            self.assertEqual(
2031
                unchecked_before,
2032
                unchecked_after.union(set([self.get_python_report()])),
2033
            )
2034
            self.assertEqual(
2035
                self.crashdb.get_fixed_version(self.get_python_report()), None
2036
            )
1326 by Martin Pitt
* apport/crashdb.py: Add new interface duplicate_of(id) to return the master
2037
1657 by Martin Pitt
Rename CrashDatabase.update() to update_traces()
2038
        def test_update_traces_invalid(self):
3510 by Benjamin Drung
Format Python code with black
2039
            """updating an invalid crash
2189 by Martin Pitt
remove trailing whitespace
2040
1337 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add test case: Update a bug report which
2041
            This simulates a race condition where a crash being processed gets
2042
            invalidated by marking it as a duplicate.
3510 by Benjamin Drung
Format Python code with black
2043
            """
3625 by Benjamin Drung
Fix pylints redefined-builtin
2044
            crash_id = self.get_segv_report(force_fresh=True)
2045
2046
            r = self.crashdb.download(crash_id)
2047
2048
            self.crashdb.close_duplicate(r, crash_id, self.get_segv_report())
1337 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add test case: Update a bug report which
2049
2117 by Martin Pitt
apport-retrace: Fix "an useful" typo. (LP: #911437)
2050
            # updating with a useful stack trace removes core dump
3510 by Benjamin Drung
Format Python code with black
2051
            r["StacktraceTop"] = (
2052
                "read () from /lib/libc.6.so\nfoo (i=1)"
2053
                " from /usr/lib/libfoo.so"
2054
            )
2055
            r["Stacktrace"] = "long\ntrace"
2056
            r["ThreadStacktrace"] = "thread\neven longer\ntrace"
3625 by Benjamin Drung
Fix pylints redefined-builtin
2057
            self.crashdb.update_traces(crash_id, r, "good retrace!")
1337 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add test case: Update a bug report which
2058
3625 by Benjamin Drung
Fix pylints redefined-builtin
2059
            r = self.crashdb.download(crash_id)
3510 by Benjamin Drung
Format Python code with black
2060
            self.assertNotIn("CoreDump", r)
1337 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add test case: Update a bug report which
2061
3638 by Benjamin Drung
Fix pylints unused-argument
2062
        @patch.object(
2063
            CrashDatabase, "_get_source_version", unittest.mock.MagicMock()
2064
        )
2065
        def test_get_fixed_version(self):
3510 by Benjamin Drung
Format Python code with black
2066
            """get_fixed_version() for fixed bugs
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2067
2068
            Other cases are already checked in test_marking_segv() (invalid
2069
            bugs) and test_duplicates (duplicate bugs) for efficiency.
3510 by Benjamin Drung
Format Python code with black
2070
            """
2414 by Martin Pitt
launchpad.py: Robustify test_get_fixed_version() against staging not having current release series
2071
            # staging.launchpad.net often does not have Quantal, so mock-patch
2072
            # it to a known value
3510 by Benjamin Drung
Format Python code with black
2073
            CrashDatabase._get_source_version.return_value = "3.14"
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2074
            self._mark_report_fixed(self.get_segv_report())
2075
            fixed_ver = self.crashdb.get_fixed_version(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
2076
            self.assertEqual(fixed_ver, "3.14")
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2077
            self._mark_report_new(self.get_segv_report())
3510 by Benjamin Drung
Format Python code with black
2078
            self.assertEqual(
2079
                self.crashdb.get_fixed_version(self.get_segv_report()), None
2080
            )
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2081
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
2082
        #
2083
        # Launchpad specific implementation and tests
2084
        #
2085
2086
        @classmethod
3615 by Benjamin Drung
Avoid global variables (where possible)
2087
        @cache
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
2088
        def _get_instance(klass):
3510 by Benjamin Drung
Format Python code with black
2089
            """Create a CrashDB instance"""
2090
2091
            launchpad_instance = (
3580 by Benjamin Drung
Add support for qastaging.launchpad.net
2092
                os.environ.get("APPORT_LAUNCHPAD_INSTANCE") or "qastaging"
3510 by Benjamin Drung
Format Python code with black
2093
            )
2094
2095
            return CrashDatabase(
2096
                os.environ.get("LP_CREDENTIALS"),
2097
                {"distro": "ubuntu", "launchpad_instance": launchpad_instance},
2098
            )
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
2099
3634 by Benjamin Drung
Mark static methods as such
2100
        @staticmethod
2101
        def _get_bug_target(db, report):
3510 by Benjamin Drung
Format Python code with black
2102
            """Return the bug_target for this report."""
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
2103
3510 by Benjamin Drung
Format Python code with black
2104
            project = db.options.get("project")
2105
            if "SourcePackage" in report:
2106
                return db.lp_distro.getSourcePackage(
2107
                    name=report["SourcePackage"]
2108
                )
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
2109
            elif project:
2110
                return db.launchpad.projects[project]
2111
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2112
        def _file_bug(self, bug_target, report, description=None):
3510 by Benjamin Drung
Format Python code with black
2113
            """File a bug report for a report.
2416 by Martin Pitt
launchpad.py: PEP8 fixes
2114
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2115
            Return the bug ID.
3510 by Benjamin Drung
Format Python code with black
2116
            """
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2117
            # unfortunately staging's +storeblob API hardly ever works, so we
2118
            # must avoid using it. Fake it by manually doing the comments and
2119
            # attachments that +filebug would ordinarily do itself when given a
2120
            # blob handle.
2121
2122
            if description is None:
3510 by Benjamin Drung
Format Python code with black
2123
                description = "some description"
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2124
2125
            mime = self.crashdb._generate_upload_blob(report)
3383 by Benjamin Drung
Drop Python 2 support
2126
            msg = email.message_from_binary_file(mime)
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2127
            mime.close()
2128
            msg_iter = msg.walk()
2129
2130
            # first one is the multipart container
3383 by Benjamin Drung
Drop Python 2 support
2131
            header = next(msg_iter)
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2132
            assert header.is_multipart()
2133
3510 by Benjamin Drung
Format Python code with black
2134
            # second part should be an inline text/plain attachments
2135
            # with all short fields
3383 by Benjamin Drung
Drop Python 2 support
2136
            part = next(msg_iter)
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2137
            assert not part.is_multipart()
3510 by Benjamin Drung
Format Python code with black
2138
            assert part.get_content_type() == "text/plain"
2139
            description += "\n\n" + part.get_payload(decode=True).decode(
2140
                "UTF-8", "replace"
2141
            )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2142
2143
            # create the bug from header and description data
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
2144
            bug = self.crashdb.launchpad.bugs.createBug(
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2145
                description=description,
2489 by Martin Pitt
* launchpad.py: Temporarily disable filing private bugs in the test suite, to work around the SSLHandshakeError error when downloading private attachments from staging.
2146
                # temporarily disabled to work around SSLHandshakeError on
2147
                # private attachments
2811 by Martin Pitt
* Adjust code to match latest pep8 checker.
2148
                # private=(header['Private'] == 'yes'),
3510 by Benjamin Drung
Format Python code with black
2149
                tags=header["Tags"].split(),
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
2150
                target=bug_target,
3510 by Benjamin Drung
Format Python code with black
2151
                title=report.get("Title", report.standard_title()),
2152
            )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2153
2154
            # nwo add the attachments
2155
            for part in msg_iter:
2156
                assert not part.is_multipart()
3510 by Benjamin Drung
Format Python code with black
2157
                bug.addAttachment(
2158
                    comment="",
2159
                    description=part.get_filename(),
2160
                    content_type=None,
2161
                    data=part.get_payload(decode=True),
2162
                    filename=part.get_filename(),
2163
                    is_patch=False,
2164
                )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2165
3510 by Benjamin Drung
Format Python code with black
2166
            for subscriber in header["Subscribers"].split():
1785.3.1 by Diogo Matsubara
use launchpadlib to file a bug instead of screen scraping
2167
                sub = self.crashdb.launchpad.people[subscriber]
2168
                if sub:
2169
                    bug.subscribe(person=sub)
2170
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2171
            return bug.id
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2172
3625 by Benjamin Drung
Fix pylints redefined-builtin
2173
        def _mark_needs_retrace(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
2174
            """Mark a report ID as needing retrace."""
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
2175
3625 by Benjamin Drung
Fix pylints redefined-builtin
2176
            bug = self.crashdb.launchpad.bugs[crash_id]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2177
            if self.crashdb.arch_tag not in bug.tags:
2178
                bug.tags = bug.tags + [self.crashdb.arch_tag]
2179
                bug.lp_save()
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
2180
3625 by Benjamin Drung
Fix pylints redefined-builtin
2181
        def _mark_needs_dupcheck(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
2182
            """Mark a report ID as needing duplicate check."""
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
2183
3625 by Benjamin Drung
Fix pylints redefined-builtin
2184
            bug = self.crashdb.launchpad.bugs[crash_id]
3510 by Benjamin Drung
Format Python code with black
2185
            if "need-duplicate-check" not in bug.tags:
2186
                bug.tags = bug.tags + ["need-duplicate-check"]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2187
                bug.lp_save()
2188
3625 by Benjamin Drung
Fix pylints redefined-builtin
2189
        def _mark_report_fixed(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
2190
            """Close a report ID as "fixed"."""
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2191
3625 by Benjamin Drung
Fix pylints redefined-builtin
2192
            bug = self.crashdb.launchpad.bugs[crash_id]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2193
            tasks = list(bug.bug_tasks)
2194
            assert len(tasks) == 1
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
2195
            t = tasks[0]
3510 by Benjamin Drung
Format Python code with black
2196
            t.status = "Fix Released"
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
2197
            t.lp_save()
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2198
3625 by Benjamin Drung
Fix pylints redefined-builtin
2199
        def _mark_report_new(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
2200
            """Reopen a report ID as "new"."""
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2201
3625 by Benjamin Drung
Fix pylints redefined-builtin
2202
            bug = self.crashdb.launchpad.bugs[crash_id]
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2203
            tasks = list(bug.bug_tasks)
2204
            assert len(tasks) == 1
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
2205
            t = tasks[0]
3510 by Benjamin Drung
Format Python code with black
2206
            t.status = "New"
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
2207
            t.lp_save()
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2208
3625 by Benjamin Drung
Fix pylints redefined-builtin
2209
        def _verify_marked_regression(self, crash_id):
3510 by Benjamin Drung
Format Python code with black
2210
            """Verify that report ID is marked as regression."""
1385 by Martin Pitt
switch apport.crashdb_impl.launchpad from python-launchpad-bugs to launchpadlib
2211
3625 by Benjamin Drung
Fix pylints redefined-builtin
2212
            bug = self.crashdb.launchpad.bugs[crash_id]
3510 by Benjamin Drung
Format Python code with black
2213
            self.assertIn("regression-retracer", bug.tags)
1329 by Martin Pitt
launchpad.py: test Python reporting and tag handling
2214
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2215
        def test_project(self):
3510 by Benjamin Drung
Format Python code with black
2216
            """reporting crashes against a project instead of a distro"""
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2217
3510 by Benjamin Drung
Format Python code with black
2218
            launchpad_instance = (
3580 by Benjamin Drung
Add support for qastaging.launchpad.net
2219
                os.environ.get("APPORT_LAUNCHPAD_INSTANCE") or "qastaging"
3510 by Benjamin Drung
Format Python code with black
2220
            )
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2221
            # crash database for langpack-o-matic project (this does not have
2222
            # packages in any distro)
3510 by Benjamin Drung
Format Python code with black
2223
            crashdb = CrashDatabase(
2224
                os.environ.get("LP_CREDENTIALS"),
2225
                {
2226
                    "project": "langpack-o-matic",
2227
                    "launchpad_instance": launchpad_instance,
2228
                },
2229
            )
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2230
            self.assertEqual(crashdb.distro, None)
2231
2232
            # create Python crash report
3510 by Benjamin Drung
Format Python code with black
2233
            r = apport.Report("Crash")
2234
            r["ExecutablePath"] = "/bin/foo"
2235
            r[
2236
                "Traceback"
2237
            ] = """Traceback (most recent call last):
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2238
  File "/bin/foo", line 67, in fuzz
1827 by Martin Pitt
Python 3 compatible "print" (not everything yet)
2239
    print(weird)
3510 by Benjamin Drung
Format Python code with black
2240
NameError: global name 'weird' is not defined"""
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2241
            r.add_os_info()
2242
            r.add_user_info()
3510 by Benjamin Drung
Format Python code with black
2243
            self.assertEqual(
2244
                r.standard_title(),
2245
                "foo crashed with NameError in fuzz():"
2246
                " global name 'weird' is not defined",
2247
            )
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2248
2249
            # file it
1788 by Martin Pitt
launchpad.py: Use launchpadlib to file a bug instead of screen scraping. The latter was completely broken with current Launchpad, so this makes the test suite actually work again. Thanks to Diogo Matsubara! - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that you can now specify "staging", "edge", or "dev" (for a local http://launchpad.dev installation). Thanks to Diogo Matsubara!
2250
            bug_target = self._get_bug_target(crashdb, r)
3510 by Benjamin Drung
Format Python code with black
2251
            self.assertEqual(bug_target.name, "langpack-o-matic")
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2252
3625 by Benjamin Drung
Fix pylints redefined-builtin
2253
            crash_id = self._file_bug(bug_target, r)
2254
            self.assertTrue(crash_id > 0)
2255
            sys.stderr.write(f"(https://{self.hostname}/bugs/{crash_id}) ")
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2256
2257
            # update
3625 by Benjamin Drung
Fix pylints redefined-builtin
2258
            r = crashdb.download(crash_id)
3510 by Benjamin Drung
Format Python code with black
2259
            r["StacktraceTop"] = (
2260
                "read () from /lib/libc.6.so\nfoo (i=1)"
2261
                " from /usr/lib/libfoo.so"
2262
            )
2263
            r["Stacktrace"] = "long\ntrace"
2264
            r["ThreadStacktrace"] = "thread\neven longer\ntrace"
3625 by Benjamin Drung
Fix pylints redefined-builtin
2265
            crashdb.update_traces(crash_id, r, "good retrace!")
2266
            r = crashdb.download(crash_id)
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2267
2268
            # test fixed version
3625 by Benjamin Drung
Fix pylints redefined-builtin
2269
            self.assertEqual(crashdb.get_fixed_version(crash_id), None)
3510 by Benjamin Drung
Format Python code with black
2270
            crashdb.close_duplicate(
3625 by Benjamin Drung
Fix pylints redefined-builtin
2271
                r, crash_id, self.get_uncommon_description_report()
3510 by Benjamin Drung
Format Python code with black
2272
            )
2273
            self.assertEqual(
3625 by Benjamin Drung
Fix pylints redefined-builtin
2274
                crashdb.duplicate_of(crash_id),
3510 by Benjamin Drung
Format Python code with black
2275
                self.get_uncommon_description_report(),
2276
            )
3625 by Benjamin Drung
Fix pylints redefined-builtin
2277
            self.assertEqual(crashdb.get_fixed_version(crash_id), "invalid")
2278
            crashdb.close_duplicate(r, crash_id, None)
2279
            self.assertEqual(crashdb.duplicate_of(crash_id), None)
2280
            self.assertEqual(crashdb.get_fixed_version(crash_id), None)
1340 by Martin Pitt
apport/crashdb_impl/launchpad.py: Support new CrashDB option "project"
2281
1453 by Martin Pitt
launchpad.py: More robust download(), fixes other part of LP: #382589
2282
        def test_download_robustness(self):
3510 by Benjamin Drung
Format Python code with black
2283
            """download() of uncommon description formats"""
1453 by Martin Pitt
launchpad.py: More robust download(), fixes other part of LP: #382589
2284
2285
            # only ProblemType/Architecture/DistroRelease in description
2415 by Martin Pitt
launchpad.py tests: lazily create uncommon description bug for speed up
2286
            r = self.crashdb.download(self.get_uncommon_description_report())
3510 by Benjamin Drung
Format Python code with black
2287
            self.assertEqual(r["ProblemType"], "Package")
2288
            self.assertEqual(r["Architecture"], "amd64")
2289
            self.assertTrue(r["DistroRelease"].startswith("Ubuntu "))
1453 by Martin Pitt
launchpad.py: More robust download(), fixes other part of LP: #382589
2290
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2291
        def test_escalation(self):
3510 by Benjamin Drung
Format Python code with black
2292
            """Escalating bugs with more than 10 duplicates"""
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2293
3510 by Benjamin Drung
Format Python code with black
2294
            launchpad_instance = (
3580 by Benjamin Drung
Add support for qastaging.launchpad.net
2295
                os.environ.get("APPORT_LAUNCHPAD_INSTANCE") or "qastaging"
3510 by Benjamin Drung
Format Python code with black
2296
            )
2297
            db = CrashDatabase(
2298
                os.environ.get("LP_CREDENTIALS"),
2299
                {
2300
                    "distro": "ubuntu",
2301
                    "launchpad_instance": launchpad_instance,
2302
                    "escalation_tag": "omgkittens",
2303
                    "escalation_subscription": "apport-hackers",
2304
                },
2305
            )
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2306
2307
            count = 0
3510 by Benjamin Drung
Format Python code with black
2308
            p = db.launchpad.people[
2309
                db.options["escalation_subscription"]
2310
            ].self_link
2885 by Martin Pitt
* Adjust launchpad crashdb testsuite to work against current Launchpad.
2311
            # needs to have 13 consecutive valid bugs without dupes
2312
            first_dup = 10070
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2313
            try:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
2314
                for b in range(first_dup, first_dup + 13):
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2315
                    count += 1
3510 by Benjamin Drung
Format Python code with black
2316
                    sys.stderr.write("%i " % b)
2317
                    db.close_duplicate(
2318
                        apport.Report(), b, self.get_segv_report()
2319
                    )
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2320
                    b = db.launchpad.bugs[self.get_segv_report()]
3510 by Benjamin Drung
Format Python code with black
2321
                    has_escalation_tag = db.options["escalation_tag"] in b.tags
2322
                    has_escalation_subscription = any(
3555 by Benjamin Drung
Fix pylint's use-a-generator
2323
                        s.person_link == p for s in b.subscriptions
3510 by Benjamin Drung
Format Python code with black
2324
                    )
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2325
                    if count <= 10:
1861 by Martin Pitt
Convert deprecated failIf()/assert_() TestCase method calls to assertFalse()/assertTrue().
2326
                        self.assertFalse(has_escalation_tag)
2327
                        self.assertFalse(has_escalation_subscription)
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2328
                    else:
1828 by Martin Pitt
replace obsolete TestCase.assert_() with TestCase.assertTrue()
2329
                        self.assertTrue(has_escalation_tag)
2330
                        self.assertTrue(has_escalation_subscription)
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2331
            finally:
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
2332
                for b in range(first_dup, first_dup + count):
3510 by Benjamin Drung
Format Python code with black
2333
                    sys.stderr.write("R%i " % b)
2099 by Martin Pitt
launchpad.py: Fix exception on tag removal, and fix test suite
2334
                    db.close_duplicate(apport.Report(), b, None)
3510 by Benjamin Drung
Format Python code with black
2335
            sys.stderr.write("\n")
1692 by Martin Pitt
launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
2336
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2337
        def test_marking_python_task_mangle(self):
3510 by Benjamin Drung
Format Python code with black
2338
            """source package task fixup for marking interpreter crashes"""
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2339
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2340
            self._mark_needs_dupcheck(self.get_python_report())
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2341
            unchecked_before = self.crashdb.get_dup_unchecked()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
2342
            self.assertIn(self.get_python_report(), unchecked_before)
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2343
2344
            # add an upstream task, and remove the package name from the
2345
            # package task; _mark_dup_checked is supposed to restore the
2346
            # package name
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2347
            b = self.crashdb.launchpad.bugs[self.get_python_report()]
2100 by Martin Pitt
launchpad.py: Current Launchpad cannot have private bugs which affect multiple projects. Fix test suite accordingly.
2348
            if b.private:
2349
                b.private = False
2350
                b.lp_save()
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
2351
            t = b.bug_tasks[0]
3510 by Benjamin Drung
Format Python code with black
2352
            t.target = self.crashdb.launchpad.distributions["ubuntu"]
1746 by Martin Pitt
launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the initial patch! (LP: #545009)
2353
            t.lp_save()
3510 by Benjamin Drung
Format Python code with black
2354
            b.addTask(target=self.crashdb.launchpad.projects["coreutils"])
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2355
2437 by Martin Pitt
* launchpad.py: Fix setting of 'Medium' importance on duplicate checking.
2356
            r = self.crashdb.download(self.get_python_report())
2357
            self.crashdb._mark_dup_checked(self.get_python_report(), r)
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2358
2359
            unchecked_after = self.crashdb.get_dup_unchecked()
2892 by Martin Pitt
launchpad.py tests: use proper assertions
2360
            self.assertNotIn(self.get_python_report(), unchecked_after)
3510 by Benjamin Drung
Format Python code with black
2361
            self.assertEqual(
2362
                unchecked_before,
2363
                unchecked_after.union(set([self.get_python_report()])),
2364
            )
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2365
2366
            # upstream task should be unmodified
2411 by Martin Pitt
* launchpad.py: Rework test suite to not use Launchpad's +storeblob facility at all any more. It almost never works on staging and is horribly slow. Fake the bug creation from a blob by manually creating the comment and attachments ourselves, and just assume that storeblob works on production. Also change the structure to allow running every test individually.
2367
            b = self.crashdb.launchpad.bugs[self.get_python_report()]
3510 by Benjamin Drung
Format Python code with black
2368
            self.assertEqual(b.bug_tasks[0].bug_target_name, "coreutils")
2369
            self.assertEqual(b.bug_tasks[0].status, "New")
2370
            self.assertEqual(b.bug_tasks[0].importance, "Undecided")
1701 by Martin Pitt
launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream tasks.
2371
2372
            # package-less distro task should have package name fixed
3510 by Benjamin Drung
Format Python code with black
2373
            self.assertEqual(
2374
                b.bug_tasks[1].bug_target_name, "coreutils (Ubuntu)"
2375
            )
2376
            self.assertEqual(b.bug_tasks[1].status, "New")
2377
            self.assertEqual(b.bug_tasks[1].importance, "Medium")
1951 by Martin Pitt
launchpad.py: Fix test_marking_python_task_mangle() check to work with current Launchpad.
2378
2379
            # should not confuse get_fixed_version()
3510 by Benjamin Drung
Format Python code with black
2380
            self.assertEqual(
2381
                self.crashdb.get_fixed_version(self.get_python_report()), None
2382
            )
1702 by Martin Pitt
launchpad.py, get_fixed_version(): Do not consider a bug as invalid just because it has any invalid distro package task.
2383
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
2384
        @classmethod
3510 by Benjamin Drung
Format Python code with black
2385
        def _generate_sigsegv_report(klass, signal="11"):
2386
            """Create a test executable which will die with a SIGSEGV, generate
2387
            a core dump for it, create a problem report with those two
2388
            arguments (ExecutablePath and CoreDump) and call add_gdb_info().
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
2389
2390
            Return the apport.report.Report.
3510 by Benjamin Drung
Format Python code with black
2391
            """
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
2392
            workdir = None
2393
            orig_cwd = os.getcwd()
2394
            pr = apport.report.Report()
2395
            try:
2396
                workdir = tempfile.mkdtemp()
2397
                atexit.register(shutil.rmtree, workdir)
2398
                os.chdir(workdir)
2399
2400
                # create a test executable
3647 by Benjamin Drung
Open files explicitly with UTF-8 encoding
2401
                with open("crash.c", "w", encoding="utf-8") as fd:
3510 by Benjamin Drung
Format Python code with black
2402
                    fd.write(
2403
                        """
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
2404
int f(x) {
2405
    int* p = 0; *p = x;
2406
    return x+1;
2407
}
2408
int main() { return f(42); }
3510 by Benjamin Drung
Format Python code with black
2409
"""
2410
                    )
2411
                assert (
2412
                    subprocess.call(["gcc", "-g", "crash.c", "-o", "crash"])
2413
                    == 0
2414
                )
2415
                assert os.path.exists("crash")
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
2416
2417
                # call it through gdb and dump core
3510 by Benjamin Drung
Format Python code with black
2418
                subprocess.call(
2419
                    [
2420
                        "gdb",
2421
                        "--batch",
2422
                        "--ex",
2423
                        "run",
2424
                        "--ex",
2425
                        "generate-core-file core",
2426
                        "./crash",
2427
                    ],
2428
                    stdout=subprocess.PIPE,
2429
                )
2430
                assert os.path.exists("core")
2431
                subprocess.check_call(["sync"])
2432
                assert (
2433
                    subprocess.call(
2434
                        ["readelf", "-n", "core"], stdout=subprocess.PIPE
2435
                    )
2436
                    == 0
2437
                )
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
2438
3510 by Benjamin Drung
Format Python code with black
2439
                pr["ExecutablePath"] = os.path.join(workdir, "crash")
2440
                pr["CoreDump"] = (os.path.join(workdir, "core"),)
2441
                pr["Signal"] = signal
2166 by Martin Pitt
launchpad.py: Fix test suite after recent test reorganization
2442
2443
                pr.add_gdb_info()
2444
            finally:
2445
                os.chdir(orig_cwd)
2446
2447
            return pr
2448
1325 by Martin Pitt
apport/crashdb_impl/launchpad.py: Add initial test suite, performing data
2449
    unittest.main()