~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
# Copyright 2009 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Module docstring goes here."""

__metaclass__ = type

import os
import shutil
from StringIO import StringIO
import tempfile
import unittest

from bzrlib.bzrdir import BzrDir
from bzrlib.tests import TestCase
from bzrlib.transport import get_transport

from devscripts import get_launchpad_root
from devscripts.sourcecode import (
    find_branches,
    interpret_config,
    parse_config_file,
    plan_update,
    )


class TestParseConfigFile(unittest.TestCase):
    """Tests for the config file parser."""

    def makeFile(self, contents):
        return StringIO(contents)

    def test_empty(self):
        # Parsing an empty config file returns an empty sequence.
        empty_file = self.makeFile("")
        self.assertEqual([], list(parse_config_file(empty_file)))

    def test_single_value(self):
        # Parsing a file containing a single key=value pair returns a sequence
        # containing the (key, value) as a list.
        config_file = self.makeFile("key value")
        self.assertEqual(
            [['key', 'value']], list(parse_config_file(config_file)))

    def test_comment_ignored(self):
        # If a line begins with a '#', then its a comment.
        comment_only = self.makeFile('# foo')
        self.assertEqual([], list(parse_config_file(comment_only)))

    def test_optional_value(self):
        # Lines in the config file can have a third optional entry.
        config_file = self.makeFile('key value optional')
        self.assertEqual(
            [['key', 'value', 'optional']],
            list(parse_config_file(config_file)))

    def test_whitespace_stripped(self):
        # Any whitespace around any of the tokens in the config file are
        # stripped out.
        config_file = self.makeFile('  key   value    optional   ')
        self.assertEqual(
            [['key', 'value', 'optional']],
            list(parse_config_file(config_file)))


class TestInterpretConfiguration(unittest.TestCase):
    """Tests for the configuration interpreter."""

    def test_empty(self):
        # An empty configuration stream means no configuration.
        config = interpret_config([], False)
        self.assertEqual({}, config)

    def test_key_value(self):
        # A (key, value) pair without a third optional value is returned in
        # the configuration as a dictionary entry under 'key' with '(value,
        # None, False)' as its value.
        config = interpret_config([['key', 'value']], False)
        self.assertEqual({'key': ('value', None, False)}, config)

    def test_key_value_public_only(self):
        # A (key, value) pair without a third optional value is returned in
        # the configuration as a dictionary entry under 'key' with '(value,
        # None, False)' as its value when public_only is true.
        config = interpret_config([['key', 'value']], True)
        self.assertEqual({'key': ('value', None, False)}, config)

    def test_key_value_optional(self):
        # A (key, value, optional) entry is returned in the configuration as a
        # dictionary entry under 'key' with '(value, True)' as its value.
        config = interpret_config([['key', 'value', 'optional']], False)
        self.assertEqual({'key': ('value', None, True)}, config)

    def test_key_value_optional_public_only(self):
        # A (key, value, optional) entry is not returned in the configuration
        # when public_only is true.
        config = interpret_config([['key', 'value', 'optional']], True)
        self.assertEqual({}, config)

    def test_key_value_revision(self):
        # A (key, value) pair without a third optional value when the
        # value has a suffix of ``;revno=[REVISION]`` is returned in the
        # configuration as a dictionary entry under 'key' with '(value,
        # None, False)' as its value.
        config = interpret_config([['key', 'value;revno=45']], False)
        self.assertEqual({'key': ('value', '45', False)}, config)

    def test_key_value_revision(self):
        # A (key, value) pair without a third optional value when the
        # value has multiple suffixes of ``;revno=[REVISION]`` raises an
        # error.
        self.assertRaises(
            AssertionError,
            interpret_config, [['key', 'value;revno=45;revno=47']], False)

    def test_too_many_values(self):
        # A line with too many values raises an error.
        self.assertRaises(
            AssertionError,
            interpret_config, [['key', 'value', 'optional', 'extra']], False)

    def test_bad_optional_value(self):
        # A third value that is not the "optional" string raises an error.
        self.assertRaises(
            AssertionError,
            interpret_config, [['key', 'value', 'extra']], False)

class TestPlanUpdate(unittest.TestCase):
    """Tests for how to plan the update."""

    def test_trivial(self):
        # In the trivial case, there are no existing branches and no
        # configured branches, so there are no branches to add, none to
        # update, and none to remove.
        new, existing, removed = plan_update([], {})
        self.assertEqual({}, new)
        self.assertEqual({}, existing)
        self.assertEqual(set(), removed)

    def test_all_new(self):
        # If there are no existing branches, then the all of the configured
        # branches are new, none are existing and none have been removed.
        new, existing, removed = plan_update([], {'a': ('b', False)})
        self.assertEqual({'a': ('b', False)}, new)
        self.assertEqual({}, existing)
        self.assertEqual(set(), removed)

    def test_all_old(self):
        # If there configuration is now empty, but there are existing
        # branches, then that means all the branches have been removed from
        # the configuration, none are new and none are updated.
        new, existing, removed = plan_update(['a', 'b', 'c'], {})
        self.assertEqual({}, new)
        self.assertEqual({}, existing)
        self.assertEqual(set(['a', 'b', 'c']), removed)

    def test_all_same(self):
        # If the set of existing branches is the same as the set of
        # non-existing branches, then they all need to be updated.
        config = {'a': ('b', False), 'c': ('d', True)}
        new, existing, removed = plan_update(config.keys(), config)
        self.assertEqual({}, new)
        self.assertEqual(config, existing)
        self.assertEqual(set(), removed)

    def test_smoke_the_default_config(self):
        # Make sure we can parse, interpret and plan based on the default
        # config file.
        root = get_launchpad_root()
        config_filename = os.path.join(root, 'utilities', 'sourcedeps.conf')
        config_file = open(config_filename)
        config = interpret_config(parse_config_file(config_file), False)
        config_file.close()
        plan_update([], config)


class TestFindBranches(TestCase):
    """Tests the way that we find branches."""

    def setUp(self):
        TestCase.setUp(self)
        self.disable_directory_isolation()

    def makeBranch(self, path):
        transport = get_transport(path)
        transport.ensure_base()
        BzrDir.create_branch_convenience(
            transport.base, possible_transports=[transport])

    def makeDirectory(self):
        directory = tempfile.mkdtemp()
        self.addCleanup(shutil.rmtree, directory)
        return directory

    def test_empty_directory_has_no_branches(self):
        # An empty directory has no branches.
        empty = self.makeDirectory()
        self.assertEqual([], list(find_branches(empty)))

    def test_directory_with_branches(self):
        # find_branches finds branches in the directory.
        directory = self.makeDirectory()
        self.makeBranch('%s/a' % directory)
        self.assertEqual(['a'], list(find_branches(directory)))

    def test_ignores_files(self):
        # find_branches ignores any files in the directory.
        directory = self.makeDirectory()
        some_file = open('%s/a' % directory, 'w')
        some_file.write('hello\n')
        some_file.close()
        self.assertEqual([], list(find_branches(directory)))