~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_msgeditor.py

  • Committer: Vincent Ladeuil
  • Date: 2012-07-31 09:22:02 UTC
  • mto: This revision was merged to the branch mainline in revision 6554.
  • Revision ID: v.ladeuil+lp@free.fr-20120731092202-qh9fs6q4p7y4qqmy
Stop using _CompatibleStack now that local config files can be
shared. Save changes when library state goes out of scope or, as a
fallback, when the process ends.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Test commit message editor.
18
18
"""
20
20
import os
21
21
import sys
22
22
 
23
 
from bzrlib.branch import Branch
24
 
from bzrlib.config import ensure_config_dir_exists, config_filename
25
 
import bzrlib.msgeditor 
26
 
from bzrlib.tests import TestCaseWithTransport, TestSkipped
 
23
from bzrlib import (
 
24
    commit,
 
25
    config,
 
26
    errors,
 
27
    msgeditor,
 
28
    osutils,
 
29
    trace,
 
30
    )
 
31
from bzrlib.msgeditor import (
 
32
    make_commit_message_template_encoded,
 
33
    edit_commit_message_encoded
 
34
)
 
35
from bzrlib.tests import (
 
36
    features,
 
37
    TestCaseInTempDir,
 
38
    TestCaseWithTransport,
 
39
    TestNotApplicable,
 
40
    TestSkipped,
 
41
    multiply_tests,
 
42
    probe_bad_non_ascii,
 
43
    split_suite_by_re,
 
44
    )
 
45
from bzrlib.tests.EncodingAdapter import encoding_scenarios
27
46
from bzrlib.trace import mutter
28
47
 
29
48
 
 
49
def load_tests(standard_tests, module, loader):
 
50
    """Parameterize the test for tempfile creation with different encodings."""
 
51
    to_adapt, result = split_suite_by_re(standard_tests,
 
52
        "test__create_temp_file_with_commit_template_in_unicode_dir")
 
53
    return multiply_tests(to_adapt, encoding_scenarios, result)
 
54
 
 
55
 
30
56
class MsgEditorTest(TestCaseWithTransport):
31
57
 
32
58
    def make_uncommitted_tree(self):
41
67
                "filesystem encoding %s" % sys.getfilesystemencoding())
42
68
        working_tree.add(filename)
43
69
        return working_tree
44
 
    
 
70
 
45
71
    def test_commit_template(self):
46
72
        """Test building a commit message template"""
47
73
        working_tree = self.make_uncommitted_tree()
48
 
        template = bzrlib.msgeditor.make_commit_message_template(working_tree, None)
49
 
        self.assertEqualDiff(template,
50
 
u"""\
51
 
added:
52
 
  hell\u00d8
53
 
""")
54
 
 
55
 
    def setUp(self):
56
 
        super(MsgEditorTest, self).setUp()
57
 
        self._bzr_editor = os.environ.get('BZR_EDITOR', None)
58
 
 
59
 
    def tearDown(self):
60
 
        if self._bzr_editor is not None:
61
 
            os.environ['BZR_EDITOR'] = self._bzr_editor
62
 
        else:
63
 
            if os.environ.get('BZR_EDITOR', None) is not None:
64
 
                del os.environ['BZR_EDITOR']
65
 
        super(MsgEditorTest, self).tearDown()
66
 
 
67
 
    def test_run_editor(self):
 
74
        template = msgeditor.make_commit_message_template(working_tree,
 
75
                                                                 None)
 
76
        self.assertEqualDiff(template,
 
77
u"""\
 
78
added:
 
79
  hell\u00d8
 
80
""")
 
81
 
 
82
    def make_multiple_pending_tree(self):
 
83
        config.GlobalStack().set('email', 'Bilbo Baggins <bb@hobbit.net>')
 
84
        tree = self.make_branch_and_tree('a')
 
85
        tree.commit('Initial checkin.', timestamp=1230912900, timezone=0)
 
86
        tree2 = tree.bzrdir.clone('b').open_workingtree()
 
87
        tree.commit('Minor tweak.', timestamp=1231977840, timezone=0)
 
88
        tree2.commit('Feature X work.', timestamp=1233186240, timezone=0)
 
89
        tree3 = tree2.bzrdir.clone('c').open_workingtree()
 
90
        tree2.commit('Feature X finished.', timestamp=1233187680, timezone=0)
 
91
        tree3.commit('Feature Y, based on initial X work.',
 
92
                     timestamp=1233285960, timezone=0)
 
93
        tree.merge_from_branch(tree2.branch)
 
94
        tree.merge_from_branch(tree3.branch, force=True)
 
95
        return tree
 
96
 
 
97
    def test_commit_template_pending_merges(self):
 
98
        """Test building a commit message template when there are pending
 
99
        merges.  The commit message should show all pending merge revisions,
 
100
        as does 'status -v', not only the merge tips.
 
101
        """
 
102
        working_tree = self.make_multiple_pending_tree()
 
103
        template = msgeditor.make_commit_message_template(working_tree, None)
 
104
        self.assertEqualDiff(template,
 
105
u"""\
 
106
pending merges:
 
107
  Bilbo Baggins 2009-01-29 Feature X finished.
 
108
    Bilbo Baggins 2009-01-28 Feature X work.
 
109
  Bilbo Baggins 2009-01-30 Feature Y, based on initial X work.
 
110
""")
 
111
 
 
112
    def test_commit_template_encoded(self):
 
113
        """Test building a commit message template"""
 
114
        working_tree = self.make_uncommitted_tree()
 
115
        template = make_commit_message_template_encoded(working_tree,
 
116
                                                        None,
 
117
                                                        output_encoding='utf8')
 
118
        self.assertEqualDiff(template,
 
119
u"""\
 
120
added:
 
121
  hell\u00d8
 
122
""".encode("utf8"))
 
123
 
 
124
 
 
125
    def test_commit_template_and_diff(self):
 
126
        """Test building a commit message template"""
 
127
        working_tree = self.make_uncommitted_tree()
 
128
        template = make_commit_message_template_encoded(working_tree,
 
129
                                                        None,
 
130
                                                        diff=True,
 
131
                                                        output_encoding='utf8')
 
132
 
 
133
        self.assertTrue("""\
 
134
@@ -0,0 +1,1 @@
 
135
+contents of hello
 
136
""" in template)
 
137
        self.assertTrue(u"""\
 
138
added:
 
139
  hell\u00d8
 
140
""".encode('utf8') in template)
 
141
 
 
142
    def make_do_nothing_editor(self, basename='fed'):
68
143
        if sys.platform == "win32":
69
 
            f = file('fed.bat', 'w')
 
144
            name = basename + '.bat'
 
145
            f = file(name, 'w')
70
146
            f.write('@rem dummy fed')
71
147
            f.close()
72
 
            os.environ['BZR_EDITOR'] = 'fed.bat'
 
148
            return name
73
149
        else:
74
 
            f = file('fed.sh', 'wb')
 
150
            name = basename + '.sh'
 
151
            f = file(name, 'wb')
75
152
            f.write('#!/bin/sh\n')
76
153
            f.close()
77
 
            os.chmod('fed.sh', 0755)
78
 
            os.environ['BZR_EDITOR'] = './fed.sh'
 
154
            os.chmod(name, 0755)
 
155
            return './' + name
79
156
 
80
 
        self.assertEqual(True, bzrlib.msgeditor._run_editor(''),
 
157
    def test_run_editor(self):
 
158
        self.overrideEnv('BZR_EDITOR', self.make_do_nothing_editor())
 
159
        self.assertEqual(True, msgeditor._run_editor(''),
81
160
                         'Unable to run dummy fake editor')
82
161
 
83
 
    def test_edit_commit_message(self):
84
 
        working_tree = self.make_uncommitted_tree()
85
 
        # make fake editor
 
162
    def test_parse_editor_name(self):
 
163
        """Correctly interpret names with spaces.
 
164
 
 
165
        See <https://bugs.launchpad.net/bzr/+bug/220331>
 
166
        """
 
167
        self.overrideEnv('BZR_EDITOR',
 
168
            '"%s"' % self.make_do_nothing_editor('name with spaces'))
 
169
        self.assertEqual(True, msgeditor._run_editor('a_filename'))    
 
170
 
 
171
    def make_fake_editor(self, message='test message from fed\\n'):
 
172
        """Set up environment so that an editor will be a known script.
 
173
 
 
174
        Sets up BZR_EDITOR so that if an editor is spawned it will run a
 
175
        script that just adds a known message to the start of the file.
 
176
        """
86
177
        f = file('fed.py', 'wb')
87
178
        f.write('#!%s\n' % sys.executable)
88
179
        f.write("""\
 
180
# coding=utf-8
89
181
import sys
90
182
if len(sys.argv) == 2:
91
183
    fn = sys.argv[1]
93
185
    s = f.read()
94
186
    f.close()
95
187
    f = file(fn, 'wb')
96
 
    f.write('test message from fed\\n')
 
188
    f.write('%s')
97
189
    f.write(s)
98
190
    f.close()
99
 
""")
 
191
""" % (message, ))
100
192
        f.close()
101
193
        if sys.platform == "win32":
102
194
            # [win32] make batch file and set BZR_EDITOR
106
198
"%s" fed.py %%1
107
199
""" % sys.executable)
108
200
            f.close()
109
 
            os.environ['BZR_EDITOR'] = 'fed.bat'
 
201
            self.overrideEnv('BZR_EDITOR', 'fed.bat')
110
202
        else:
111
203
            # [non-win32] make python script executable and set BZR_EDITOR
112
204
            os.chmod('fed.py', 0755)
113
 
            os.environ['BZR_EDITOR'] = './fed.py'
 
205
            self.overrideEnv('BZR_EDITOR', './fed.py')
 
206
 
 
207
    def test_edit_commit_message(self):
 
208
        working_tree = self.make_uncommitted_tree()
 
209
        self.make_fake_editor()
114
210
 
115
211
        mutter('edit_commit_message without infotext')
116
212
        self.assertEqual('test message from fed\n',
117
 
                         bzrlib.msgeditor.edit_commit_message(''))
 
213
                         msgeditor.edit_commit_message(''))
 
214
 
 
215
        mutter('edit_commit_message with ascii string infotext')
 
216
        self.assertEqual('test message from fed\n',
 
217
                         msgeditor.edit_commit_message('spam'))
118
218
 
119
219
        mutter('edit_commit_message with unicode infotext')
120
220
        self.assertEqual('test message from fed\n',
121
 
                         bzrlib.msgeditor.edit_commit_message(u'\u1234'))
 
221
                         msgeditor.edit_commit_message(u'\u1234'))
 
222
 
 
223
        tmpl = edit_commit_message_encoded(u'\u1234'.encode("utf8"))
 
224
        self.assertEqual('test message from fed\n', tmpl)
 
225
 
 
226
    def test_start_message(self):
 
227
        self.make_uncommitted_tree()
 
228
        self.make_fake_editor()
 
229
        self.assertEqual('test message from fed\nstart message\n',
 
230
                         msgeditor.edit_commit_message('',
 
231
                                              start_message='start message\n'))
 
232
        self.assertEqual('test message from fed\n',
 
233
                         msgeditor.edit_commit_message('',
 
234
                                              start_message=''))
122
235
 
123
236
    def test_deleted_commit_message(self):
124
237
        working_tree = self.make_uncommitted_tree()
125
238
 
126
239
        if sys.platform == 'win32':
127
 
            os.environ['BZR_EDITOR'] = 'cmd.exe /c del'
 
240
            editor = 'cmd.exe /c del'
128
241
        else:
129
 
            os.environ['BZR_EDITOR'] = 'rm'
 
242
            editor = 'rm'
 
243
        self.overrideEnv('BZR_EDITOR', editor)
130
244
 
131
 
        self.assertRaises((IOError, OSError), bzrlib.msgeditor.edit_commit_message, '')
 
245
        self.assertRaises((IOError, OSError), msgeditor.edit_commit_message, '')
132
246
 
133
247
    def test__get_editor(self):
134
 
        # Test that _get_editor can return a decent list of items
135
 
        bzr_editor = os.environ.get('BZR_EDITOR')
136
 
        visual = os.environ.get('VISUAL')
137
 
        editor = os.environ.get('EDITOR')
 
248
        self.overrideEnv('BZR_EDITOR', 'bzr_editor')
 
249
        self.overrideEnv('VISUAL', 'visual')
 
250
        self.overrideEnv('EDITOR', 'editor')
 
251
 
 
252
        conf = config.GlobalStack()
 
253
        conf.store._load_from_string('[DEFAULT]\neditor = config_editor\n')
 
254
        conf.store.save()
 
255
        editors = list(msgeditor._get_editor())
 
256
        editors = [editor for (editor, cfg_src) in editors]
 
257
 
 
258
        self.assertEqual(['bzr_editor', 'config_editor', 'visual', 'editor'],
 
259
                         editors[:4])
 
260
 
 
261
        if sys.platform == 'win32':
 
262
            self.assertEqual(['wordpad.exe', 'notepad.exe'], editors[4:])
 
263
        else:
 
264
            self.assertEqual(['/usr/bin/editor', 'vi', 'pico', 'nano', 'joe'],
 
265
                             editors[4:])
 
266
 
 
267
 
 
268
    def test__run_editor_EACCES(self):
 
269
        """If running a configured editor raises EACESS, the user is warned."""
 
270
        self.overrideEnv('BZR_EDITOR', 'eacces.py')
 
271
        f = file('eacces.py', 'wb')
 
272
        f.write('# Not a real editor')
 
273
        f.close()
 
274
        # Make the fake editor unreadable (and unexecutable)
 
275
        os.chmod('eacces.py', 0)
 
276
        # Set $EDITOR so that _run_editor will terminate before trying real
 
277
        # editors.
 
278
        self.overrideEnv('EDITOR', self.make_do_nothing_editor())
 
279
        # Call _run_editor, capturing mutter.warning calls.
 
280
        warnings = []
 
281
        def warning(*args):
 
282
            if len(args) > 1:
 
283
                warnings.append(args[0] % args[1:])
 
284
            else:
 
285
                warnings.append(args[0])
 
286
        _warning = trace.warning
 
287
        trace.warning = warning
138
288
        try:
139
 
            os.environ['BZR_EDITOR'] = 'bzr_editor'
140
 
            os.environ['VISUAL'] = 'visual'
141
 
            os.environ['EDITOR'] = 'editor'
142
 
 
143
 
            ensure_config_dir_exists()
144
 
            f = open(config_filename(), 'wb')
145
 
            f.write('editor = config_editor\n')
146
 
            f.close()
147
 
 
148
 
            editors = list(bzrlib.msgeditor._get_editor())
149
 
 
150
 
            self.assertEqual(['bzr_editor', 'config_editor', 'visual',
151
 
                              'editor'], editors[:4])
152
 
 
153
 
            if sys.platform == 'win32':
154
 
                self.assertEqual(['wordpad.exe', 'notepad.exe'], editors[4:])
155
 
            else:
156
 
                self.assertEqual(['/usr/bin/editor', 'vi', 'pico', 'nano',
157
 
                                  'joe'], editors[4:])
158
 
 
 
289
            msgeditor._run_editor('')
159
290
        finally:
160
 
            # Restore the environment
161
 
            if bzr_editor is None:
162
 
                del os.environ['BZR_EDITOR']
163
 
            else:
164
 
                os.environ['BZR_EDITOR'] = bzr_editor
165
 
            if visual is None:
166
 
                del os.environ['VISUAL']
167
 
            else:
168
 
                os.environ['VISUAL'] = visual
169
 
            if editor is None:
170
 
                del os.environ['EDITOR']
171
 
            else:
172
 
                os.environ['EDITOR'] = editor
 
291
            trace.warning = _warning
 
292
        self.assertStartsWith(warnings[0], 'Could not start editor "eacces.py"')
 
293
 
 
294
    def test__create_temp_file_with_commit_template(self):
 
295
        # check that commit template written properly
 
296
        # and has platform native line-endings (CRLF on win32)
 
297
        create_file = msgeditor._create_temp_file_with_commit_template
 
298
        msgfilename, hasinfo = create_file('infotext','----','start message')
 
299
        self.assertNotEqual(None, msgfilename)
 
300
        self.assertTrue(hasinfo)
 
301
        expected = os.linesep.join(['start message',
 
302
                                    '',
 
303
                                    '',
 
304
                                    '----',
 
305
                                    '',
 
306
                                    'infotext'])
 
307
        self.assertFileEqual(expected, msgfilename)
 
308
 
 
309
    def test__create_temp_file_with_commit_template_in_unicode_dir(self):
 
310
        self.requireFeature(features.UnicodeFilenameFeature)
 
311
        if hasattr(self, 'info'):
 
312
            tmpdir = self.info['directory']
 
313
            os.mkdir(tmpdir)
 
314
            # Force the creation of temp file in a directory whose name
 
315
            # requires some encoding support
 
316
            msgeditor._create_temp_file_with_commit_template('infotext',
 
317
                                                             tmpdir=tmpdir)
 
318
        else:
 
319
            raise TestNotApplicable('Test run elsewhere with non-ascii data.')
 
320
 
 
321
    def test__create_temp_file_with_empty_commit_template(self):
 
322
        # empty file
 
323
        create_file = msgeditor._create_temp_file_with_commit_template
 
324
        msgfilename, hasinfo = create_file('')
 
325
        self.assertNotEqual(None, msgfilename)
 
326
        self.assertFalse(hasinfo)
 
327
        self.assertFileEqual('', msgfilename)
 
328
 
 
329
    def test_unsupported_encoding_commit_message(self):
 
330
        self.overrideEnv('LANG', 'C')
 
331
        # LANG env variable has no effect on Windows
 
332
        # but some characters anyway cannot be represented
 
333
        # in default user encoding
 
334
        char = probe_bad_non_ascii(osutils.get_user_encoding())
 
335
        if char is None:
 
336
            raise TestSkipped('Cannot find suitable non-ascii character '
 
337
                'for user_encoding (%s)' % osutils.get_user_encoding())
 
338
 
 
339
        self.make_fake_editor(message=char)
 
340
 
 
341
        working_tree = self.make_uncommitted_tree()
 
342
        self.assertRaises(errors.BadCommitMessageEncoding,
 
343
                          msgeditor.edit_commit_message, '')
 
344
 
 
345
    def test_set_commit_message_no_hooks(self):
 
346
        commit_obj = commit.Commit()
 
347
        self.assertIs(None,
 
348
            msgeditor.set_commit_message(commit_obj))
 
349
 
 
350
    def test_set_commit_message_hook(self):
 
351
        msgeditor.hooks.install_named_hook("set_commit_message",
 
352
                lambda commit_obj, existing_message: "save me some typing\n", None)
 
353
        commit_obj = commit.Commit()
 
354
        self.assertEquals("save me some typing\n",
 
355
            msgeditor.set_commit_message(commit_obj))
 
356
 
 
357
    def test_generate_commit_message_template_no_hooks(self):
 
358
        commit_obj = commit.Commit()
 
359
        self.assertIs(None,
 
360
            msgeditor.generate_commit_message_template(commit_obj))
 
361
 
 
362
    def test_generate_commit_message_template_hook(self):
 
363
        msgeditor.hooks.install_named_hook("commit_message_template",
 
364
                lambda commit_obj, msg: "save me some typing\n", None)
 
365
        commit_obj = commit.Commit()
 
366
        self.assertEquals("save me some typing\n",
 
367
            msgeditor.generate_commit_message_template(commit_obj))
 
368
 
 
369
 
 
370
# GZ 2009-11-17: This wants moving to osutils when the errno checking code is
 
371
class TestPlatformErrnoWorkarounds(TestCaseInTempDir):
 
372
    """Ensuring workarounds enshrined in code actually serve a purpose"""
 
373
 
 
374
    def test_subprocess_call_bad_file(self):
 
375
        if sys.platform != "win32":
 
376
            raise TestNotApplicable("Workarounds for windows only")
 
377
        import subprocess, errno
 
378
        ERROR_BAD_EXE_FORMAT = 193
 
379
        file("textfile.txt", "w").close()
 
380
        e = self.assertRaises(WindowsError, subprocess.call, "textfile.txt")
 
381
        self.assertEqual(e.errno, errno.ENOEXEC)
 
382
        self.assertEqual(e.winerror, ERROR_BAD_EXE_FORMAT)