~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# Copyright 2010 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Test ChangesFile functionality."""

__metaclass__ = type

import os

from debian.deb822 import Changes
from zope.component import getUtility

from canonical.launchpad.ftests import import_public_test_keys
from canonical.testing.layers import (
    LaunchpadZopelessLayer,
    ZopelessDatabaseLayer,
    )
from lp.archiveuploader.changesfile import (
    CannotDetermineFileTypeError,
    ChangesFile,
    determine_file_class_and_name,
    )
from lp.archiveuploader.dscfile import DSCFile
from lp.archiveuploader.nascentuploadfile import (
    DdebBinaryUploadFile,
    DebBinaryUploadFile,
    SourceUploadFile,
    UdebBinaryUploadFile,
    UploadError,
    )
from lp.archiveuploader.tests import (
    AbsolutelyAnythingGoesUploadPolicy,
    datadir,
    )
from lp.archiveuploader.uploadpolicy import InsecureUploadPolicy
from lp.registry.interfaces.person import IPersonSet
from lp.services.log.logger import BufferLogger
from lp.testing import TestCase
from lp.testing.keyserver import KeyServerTac


class TestDetermineFileClassAndName(TestCase):

    def testSourceFile(self):
        # A non-DSC source file is a SourceUploadFile.
        self.assertEquals(
            ('foo', SourceUploadFile),
            determine_file_class_and_name('foo_1.0.diff.gz'))

    def testDSCFile(self):
        # A DSC is a DSCFile, since they're special.
        self.assertEquals(
            ('foo', DSCFile),
            determine_file_class_and_name('foo_1.0.dsc'))

    def testDEBFile(self):
        # A binary file is the appropriate PackageUploadFile subclass.
        self.assertEquals(
            ('foo', DebBinaryUploadFile),
            determine_file_class_and_name('foo_1.0_all.deb'))
        self.assertEquals(
            ('foo', DdebBinaryUploadFile),
            determine_file_class_and_name('foo_1.0_all.ddeb'))
        self.assertEquals(
            ('foo', UdebBinaryUploadFile),
            determine_file_class_and_name('foo_1.0_all.udeb'))

    def testUnmatchingFile(self):
        # Files with unknown extensions or none at all are not
        # identified.
        self.assertRaises(
            CannotDetermineFileTypeError,
            determine_file_class_and_name,
            'foo_1.0.notdsc')
        self.assertRaises(
            CannotDetermineFileTypeError,
            determine_file_class_and_name,
            'foo')


class ChangesFileTests(TestCase):
    """Tests for ChangesFile."""

    layer = LaunchpadZopelessLayer

    def setUp(self):
        super(ChangesFileTests, self).setUp()
        self.logger = BufferLogger()
        self.policy = AbsolutelyAnythingGoesUploadPolicy()

    def createChangesFile(self, filename, changes):
        tempdir = self.makeTemporaryDirectory()
        path = os.path.join(tempdir, filename)
        changes_fd = open(path, "w")
        try:
            changes.dump(changes_fd)
        finally:
            changes_fd.close()
        return ChangesFile(path, self.policy, self.logger)

    def getBaseChanges(self):
        contents = Changes()
        contents["Source"] = "mypkg"
        contents["Binary"] = "binary"
        contents["Date"] = "Fri, 25 Jun 2010 11:20:22 -0600"
        contents["Architecture"] = "i386"
        contents["Version"] = "0.1"
        contents["Distribution"] = "nifty"
        contents["Maintainer"] = "Somebody"
        contents["Changes"] = "Something changed"
        contents["Description"] = "\n An awesome package."
        contents["Changed-By"] = "Somebody <somebody@ubuntu.com>"
        contents["Files"] = [{
            "md5sum": "d2bd347b3fed184fe28e112695be491c",
            "size": "1791",
            "section": "python",
            "priority": "optional",
            "name": "dulwich_0.4.1-1.dsc"}]
        return contents

    def test_newline_in_Binary_field(self):
        # Test that newlines in Binary: fields are accepted
        contents = self.getBaseChanges()
        contents["Binary"] = "binary1\n binary2 \n binary3"
        changes = self.createChangesFile("mypkg_0.1_i386.changes", contents)
        self.assertEquals(set(["binary1", "binary2", "binary3"]), changes.binaries)

    def test_checkFileName(self):
        # checkFileName() yields an UploadError if the filename is invalid.
        contents = self.getBaseChanges()
        changes = self.createChangesFile("mypkg_0.1_i386.changes", contents)
        self.assertEquals([], list(changes.checkFileName()))
        changes = self.createChangesFile("mypkg_0.1.changes", contents)
        errors = list(changes.checkFileName())
        self.assertIsInstance(errors[0], UploadError)
        self.assertEquals(1, len(errors))

    def test_filename(self):
        # Filename gets set to the basename of the changes file on disk.
        changes = self.createChangesFile(
            "mypkg_0.1_i386.changes", self.getBaseChanges())
        self.assertEquals("mypkg_0.1_i386.changes", changes.filename)

    def test_suite_name(self):
        # The suite name gets extracted from the changes file.
        changes = self.createChangesFile(
            "mypkg_0.1_i386.changes", self.getBaseChanges())
        self.assertEquals("nifty", changes.suite_name)

    def test_version(self):
        # The version gets extracted from the changes file.
        changes = self.createChangesFile(
            "mypkg_0.1_i386.changes", self.getBaseChanges())
        self.assertEquals("0.1", changes.version)

    def test_architectures(self):
        # The architectures get extracted from the changes file
        # and parsed correctly.
        changes = self.createChangesFile(
            "mypkg_0.1_i386.changes", self.getBaseChanges())
        self.assertEquals("i386", changes.architecture_line)
        self.assertEquals(set(["i386"]), changes.architectures)

    def test_source(self):
        # The source package name gets extracted from the changes file.
        changes = self.createChangesFile(
            "mypkg_0.1_i386.changes", self.getBaseChanges())
        self.assertEquals("mypkg", changes.source)

    def test_processAddresses(self):
        # processAddresses parses the changes file and sets the
        # changed_by field.
        contents = self.getBaseChanges()
        changes = self.createChangesFile(
            "mypkg_0.1_i386.changes", contents)
        self.assertEquals(None, changes.changed_by)
        errors = list(changes.processAddresses())
        self.assertEquals(0, len(errors), "Errors: %r" % errors)
        self.assertEquals(
            "Somebody <somebody@ubuntu.com>", changes.changed_by['rfc822'])

    def test_simulated_changelog(self):
        # The simulated_changelog property returns a changelog entry based on
        # the control fields.
        contents = self.getBaseChanges()
        changes = self.createChangesFile(
            "mypkg_0.1_i386.changes", contents)
        self.assertEquals([], list(changes.processAddresses()))
        self.assertEquals(
            "Something changed\n"
            " -- Somebody <somebody@ubuntu.com>   "
            "Fri, 25 Jun 2010 11:20:22 -0600",
            changes.simulated_changelog)

    def test_requires_changed_by(self):
        # A changes file is rejected if it does not have a Changed-By field.
        contents = self.getBaseChanges()
        del contents["Changed-By"]
        self.assertRaises(
            UploadError,
            self.createChangesFile, "mypkg_0.1_i386.changes", contents)


class TestSignatureVerification(TestCase):

    layer = ZopelessDatabaseLayer

    def setUp(self):
        super(TestSignatureVerification, self).setUp()
        self.useFixture(KeyServerTac())
        import_public_test_keys()

    def test_valid_signature_accepted(self):
        # A correctly signed changes file is excepted, and all its
        # content is parsed.
        path = datadir('signatures/signed.changes')
        parsed = ChangesFile(path, InsecureUploadPolicy(), BufferLogger())
        self.assertEqual(
            getUtility(IPersonSet).getByEmail('foo.bar@canonical.com'),
            parsed.signer)
        expected = "\AFormat: 1.7\n.*foo_1.0-1.diff.gz\Z"
        self.assertTextMatchesExpressionIgnoreWhitespace(
            expected,
            parsed.parsed_content)

    def test_no_signature_rejected(self):
        # An unsigned changes file is rejected.
        path = datadir('signatures/unsigned.changes')
        self.assertRaises(
            UploadError,
            ChangesFile, path, InsecureUploadPolicy(), BufferLogger())

    def test_prefix_ignored(self):
        # A signed changes file with an unsigned prefix has only the
        # signed part parsed.
        path = datadir('signatures/prefixed.changes')
        parsed = ChangesFile(path, InsecureUploadPolicy(), BufferLogger())
        self.assertEqual(
            getUtility(IPersonSet).getByEmail('foo.bar@canonical.com'),
            parsed.signer)
        expected = "\AFormat: 1.7\n.*foo_1.0-1.diff.gz\Z"
        self.assertTextMatchesExpressionIgnoreWhitespace(
            expected,
            parsed.parsed_content)
        self.assertEqual("breezy", parsed.suite_name)
        self.assertNotIn("evil", parsed.changes_comment)