1 # -*- coding: utf-8; -*-
4 # Part of ‘dput’, a Debian package upload toolkit.
6 # This is free software, and you are welcome to redistribute it under
7 # certain conditions; see the end of this file for copyright
8 # information, grant of license, and disclaimer of warranty.
10 """ Unit tests for ‘dcut’ module. """
12 from __future__
import (absolute_import
, unicode_literals
)
28 from dput
.helper
import dputhelper
32 EXIT_STATUS_FAILURE
, EXIT_STATUS_SUCCESS
,
46 patch_subprocess_call
,
47 patch_subprocess_check_call
,
48 patch_subprocess_popen
,
50 patch_system_interfaces
,
51 patch_tempfile_mkdtemp
,
53 setup_file_double_behaviour
,
54 setup_subprocess_double_behaviour
,
56 from .test_changesfile
import (
57 make_changes_file_scenarios
,
58 set_changes_file_scenario
,
60 from .test_configfile
import (
63 from . import test_dput_main
64 from .test_dputhelper
import (
66 patch_pkg_resources_get_distribution
,
70 dummy_pwent
= PasswdEntry(
75 pw_gecos
="Lorem Ipsum,spam,eggs,beans",
76 pw_dir
=tempfile
.mktemp(),
77 pw_shell
=tempfile
.mktemp())
80 def patch_getoptions(testcase
):
81 """ Patch the `getoptions` function for this test case. """
93 if not hasattr(testcase
, 'getoptions_opts'):
94 testcase
.getoptions_opts
= {}
95 if not hasattr(testcase
, 'getoptions_args'):
96 testcase
.getoptions_args
= []
98 def fake_getoptions():
99 options
= dict(default_options
)
100 options
.update(testcase
.getoptions_opts
)
101 arguments
= list(testcase
.getoptions_args
)
102 result
= (options
, arguments
)
105 func_patcher
= mock
.patch
.object(
106 dput
.dcut
, "getoptions", autospec
=True,
107 side_effect
=fake_getoptions
)
109 testcase
.addCleanup(func_patcher
.stop
)
112 def get_upload_method_func(testcase
):
113 """ Get the specified upload method. """
114 host
= testcase
.test_host
115 method_name
= testcase
.runtime_config_parser
.get(host
, 'method')
116 method_func
= testcase
.upload_methods
[method_name
]
120 class make_usage_message_TestCase(testtools
.TestCase
):
121 """ Test cases for `make_usage_message` function. """
124 """ Set up test fixtures. """
125 super(make_usage_message_TestCase
, self
).setUp()
129 def test_returns_text_with_program_name(self
):
130 """ Should return text with expected program name. """
131 result
= dput
.dcut
.make_usage_message()
132 expected_result
= textwrap
.dedent("""\
133 Usage: {progname} ...
135 """).format(progname
=self
.progname
)
138 testtools
.matchers
.DocTestMatches(
139 expected_result
, flags
=doctest
.ELLIPSIS
))
142 class getoptions_TestCase(testtools
.TestCase
):
143 """ Base for test cases for `getoptions` function. """
145 default_options
= NotImplemented
147 scenarios
= NotImplemented
150 """ Set up test fixtures. """
151 super(getoptions_TestCase
, self
).setUp()
152 patch_system_interfaces(self
)
154 patch_os_environ(self
)
155 patch_os_getuid(self
)
156 patch_pwd_getpwuid(self
)
159 self
.patch_etc_mailname()
160 setup_file_double_behaviour(
161 self
, [self
.mailname_file_double
])
163 self
.set_hostname_subprocess_double()
164 patch_subprocess_popen(self
)
167 if hasattr(self
, 'expected_options'):
168 self
.set_expected_result()
170 self
.patch_distribution()
171 self
.patch_make_usage_message()
173 def patch_etc_mailname(self
):
174 """ Patch the ‘/etc/mailname’ file. """
175 path
= "/etc/mailname"
176 if hasattr(self
, 'mailname_fake_file'):
177 double
= FileDouble(path
, self
.mailname_fake_file
)
179 double
= FileDouble(path
, StringIO())
180 if hasattr(self
, 'mailname_file_open_scenario_name'):
181 double
.set_open_scenario(self
.mailname_file_open_scenario_name
)
182 self
.mailname_file_double
= double
184 def set_hostname_subprocess_double(self
):
185 """ Set the test double for the ‘hostname’ subprocess. """
186 path
= "/bin/hostname"
187 argv
= (path
, "--fqdn")
188 double
= SubprocessDouble(path
, argv
=argv
)
189 double
.register_for_testcase(self
)
191 double
.set_subprocess_popen_scenario('success')
192 double
.set_stdout_content(getattr(self
, 'hostname_stdout_content', ""))
194 self
.hostname_subprocess_double
= double
196 def patch_getopt(self
):
197 """ Patch the `dputhelper.getopt` function. """
198 if not hasattr(self
, 'getopt_opts'):
199 self
.getopt_opts
= []
201 self
.getopt_opts
= list(self
.getopt_opts
)
202 if not hasattr(self
, 'getopt_args'):
203 self
.getopt_args
= []
205 self
.getopt_args
= list(self
.getopt_args
)
209 def set_expected_result(self
):
210 """ Set the expected result value. """
211 if not hasattr(self
, 'expected_arguments'):
212 self
.expected_arguments
= []
213 expected_options
= self
.default_options
.copy()
214 expected_options
.update(self
.expected_options
)
215 self
.expected_result
= (expected_options
, self
.expected_arguments
)
217 def patch_distribution(self
):
218 """ Patch the Python distribution for this test case. """
219 self
.fake_distribution
= mock
.MagicMock(pkg_resources
.Distribution
)
220 if hasattr(self
, 'dcut_version'):
221 self
.fake_distribution
.version
= self
.dcut_version
222 patch_pkg_resources_get_distribution(self
)
224 def patch_make_usage_message(self
):
225 """ Patch the `make_usage_message` function. """
226 if hasattr(self
, 'dcut_usage_message'):
227 text
= self
.dcut_usage_message
229 text
= self
.getUniqueString()
230 func_patcher
= mock
.patch
.object(
231 dput
.dcut
, "make_usage_message", autospec
=True,
234 self
.addCleanup(func_patcher
.stop
)
237 class getoptions_UploaderTestCase(
238 testscenarios
.WithScenarios
,
239 getoptions_TestCase
):
240 """ Test cases for `getoptions` function, determining uploader. """
242 environ_scenarios
= [
246 ('environ-email-not-delimited', {
247 'os_environ': {'EMAIL': "quux@example.org"},
248 'expected_environ_uploader': "<quux@example.org>",
250 ('environ-email-delimited', {
251 'os_environ': {'EMAIL': "<quux@example.org>"},
252 'expected_environ_uploader': "<quux@example.org>",
254 ('environ-debemail-not-delimited', {
255 'os_environ': {'DEBEMAIL': "flup@example.org"},
256 'expected_environ_uploader': "<flup@example.org>",
258 ('environ-debemail-delimited', {
259 'os_environ': {'DEBEMAIL': "<flup@example.org>"},
260 'expected_environ_uploader': "<flup@example.org>",
262 ('environ-both-email-and-debfullname', {
264 'EMAIL': "quux@example.org",
265 'DEBFULLNAME': "Lorem Ipsum",
267 'expected_environ_uploader': "Lorem Ipsum <quux@example.org>",
269 ('environ-both-debemail-and-debfullname', {
271 'DEBEMAIL': "flup@example.org",
272 'DEBFULLNAME': "Lorem Ipsum",
274 'expected_environ_uploader': "Lorem Ipsum <flup@example.org>",
276 ('environ-both-email-and-debemail', {
278 'EMAIL': "quux@example.org",
279 'DEBEMAIL': "flup@example.org",
281 'expected_environ_uploader': "<flup@example.org>",
283 ('environ-both-email-and-debemail-and-debfullname', {
285 'EMAIL': "quux@example.org",
286 'DEBEMAIL': "flup@example.org",
287 'DEBFULLNAME': "Lorem Ipsum",
289 'expected_environ_uploader': "Lorem Ipsum <flup@example.org>",
294 ('domain-from-mailname-file', {
295 'mailname_fake_file': StringIO("consecteur.example.org"),
296 'pwd_getpwuid_return_value': dummy_pwent
._replace
(
298 pw_gecos
="Dolor Sit Amet,spam,beans,eggs"),
299 'expected_debug_chatter': textwrap
.dedent("""\
302 'expected_system_uploader':
303 "Dolor Sit Amet <grue@consecteur.example.org>",
305 ('domain-from-hostname-command', {
306 'mailname_file_open_scenario_name': "read_denied",
307 'hostname_stdout_content': "consecteur.example.org\n",
308 'pwd_getpwuid_return_value': dummy_pwent
._replace
(
310 pw_gecos
="Dolor Sit Amet,spam,beans,eggs"),
311 'expected_debug_chatter': textwrap
.dedent("""\
313 D: Guessing uploader: /etc/mailname was a failure
315 'expected_system_uploader':
316 "Dolor Sit Amet <grue@consecteur.example.org>",
319 'mailname_file_open_scenario_name': "read_denied",
320 'hostname_stdout_content': "",
321 'pwd_getpwuid_return_value': dummy_pwent
._replace
(
323 pw_gecos
="Dolor Sit Amet,spam,beans,eggs"),
324 'expected_debug_chatter': textwrap
.dedent("""\
326 D: Guessing uploader: /etc/mailname was a failure
327 D: Couldn't guess uploader
332 scenarios
= testscenarios
.multiply_scenarios(
333 environ_scenarios
, system_scenarios
)
335 def setUp(self
, *args
, **kwargs
):
336 """ Set up test fixtures. """
337 super(getoptions_UploaderTestCase
, self
).setUp(*args
, **kwargs
)
339 self
.set_expected_uploader()
341 def set_expected_uploader(self
):
342 """ Set the expected uploader value for this test case. """
344 'expected_command_line_uploader',
345 'expected_environ_uploader',
346 'expected_system_uploader']:
347 if hasattr(self
, attrib_name
):
348 self
.expected_uploader
= getattr(self
, attrib_name
)
351 def test_emits_debug_message_for_uploader_discovery(self
):
352 """ Should emit debug message for uploader discovery. """
353 sys
.argv
.insert(1, "--debug")
354 dput
.dcut
.getoptions()
355 expected_output_lines
= [
356 "D: trying to get maintainer email from environment"]
357 if hasattr(self
, 'expected_environ_uploader'):
358 guess_line_template
= "D: Uploader from env: {uploader}"
360 expected_output_lines
.extend(
361 self
.expected_debug_chatter
.split("\n")[:-1])
362 if hasattr(self
, 'expected_system_uploader'):
363 guess_line_template
= "D: Guessed uploader: {uploader}"
364 if hasattr(self
, 'expected_uploader'):
365 expected_output_lines
.append(guess_line_template
.format(
366 uploader
=self
.expected_uploader
))
367 expected_output
= "\n".join(expected_output_lines
)
368 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
371 class getoptions_ParseCommandLineTestCase(
372 testscenarios
.WithScenarios
,
373 getoptions_TestCase
):
374 """ Test cases for `getoptions` function, parsing command line. """
376 dcut_usage_message
= "Lorem ipsum, dolor sit amet."
379 dcut_version
= "ipsum"
381 config_file_path
= tempfile
.mktemp()
382 changes_file_path
= tempfile
.mktemp()
383 output_file_path
= tempfile
.mktemp()
384 upload_file_path
= tempfile
.mktemp()
386 default_options
= dict()
387 default_options
.update(
388 (key
, None) for key
in [
389 'config', 'host', 'uploader', 'keyid',
390 'filetocreate', 'filetoupload', 'changes'])
391 default_options
.update(
392 (key
, False) for key
in ['debug', 'simulate', 'passive'])
399 'getopt_opts': [("--b0gUs", "BOGUS")],
400 'expected_stderr_output': (
401 "{progname} internal error:"
402 " Option --b0gUs, argument BOGUS unknown").format(
404 'expected_exit_status': EXIT_STATUS_FAILURE
,
407 'getopt_opts': [("--help", None)],
408 'expected_stdout_output': dcut_usage_message
,
409 'expected_exit_status': EXIT_STATUS_SUCCESS
,
412 'getopt_opts': [("--version", None)],
413 'expected_stdout_output': " ".join(
414 [progname
, dcut_version
]),
415 'expected_exit_status': EXIT_STATUS_SUCCESS
,
417 ('option-filetoupload-and-environ-uploader', {
419 'DEBEMAIL': "flup@example.org",
420 'DEBFULLNAME': "Lorem Ipsum",
423 ("--upload", upload_file_path
),
425 'expected_options': {
426 'uploader': "Lorem Ipsum <flup@example.org>",
427 'filetoupload': upload_file_path
,
429 'expected_arguments': [],
431 ('option-changes-and-environ-uploader', {
433 'DEBEMAIL': "flup@example.org",
434 'DEBFULLNAME': "Lorem Ipsum",
437 ("--input", changes_file_path
),
439 'expected_options': {
440 'uploader': "Lorem Ipsum <flup@example.org>",
441 'changes': changes_file_path
,
443 'expected_arguments': [],
445 ('option-filetoupload-and-option-maintaineraddress', {
447 ("--upload", upload_file_path
),
448 ("--maintaineraddress", "Lorem Ipsum <flup@example.org>"),
450 'expected_options': {
451 'uploader': "Lorem Ipsum <flup@example.org>",
452 'filetoupload': upload_file_path
,
454 'expected_arguments': [],
456 ('option-changes-and-option-maintaineraddress', {
458 ("--input", changes_file_path
),
459 ("--maintaineraddress", "Lorem Ipsum <flup@example.org>"),
461 'expected_options': {
462 'uploader': "Lorem Ipsum <flup@example.org>",
463 'changes': changes_file_path
,
465 'expected_arguments': [],
467 ('option-filetoupload-with-no-uploader', {
468 'getopt_opts': [("--upload", upload_file_path
)],
469 'expected_stderr_output': "command file cannot be created",
470 'expected_exit_status': EXIT_STATUS_FAILURE
,
472 ('option-changes-with-no-uploader', {
473 'getopt_opts': [("--input", changes_file_path
)],
474 'expected_stderr_output': "command file cannot be created",
475 'expected_exit_status': EXIT_STATUS_FAILURE
,
480 ("--simulate", None),
481 ("--config", config_file_path
),
482 ("--maintaineraddress", "Lorem Ipsum <flup@example.org>"),
483 ("--keyid", "DEADBEEF"),
485 ("--output", output_file_path
),
486 ("--host", "quux.example.com"),
488 'expected_options': {
491 'config': config_file_path
,
492 'uploader': "Lorem Ipsum <flup@example.org>",
495 'filetocreate': output_file_path
,
496 'host': "quux.example.com",
498 'expected_arguments': [],
502 scenarios
= option_scenarios
504 def test_emits_debug_message_for_program_version(self
):
505 """ Should emit debug message for program version. """
506 sys
.argv
.insert(1, "--debug")
507 expected_progname
= self
.progname
508 expected_version
= self
.dcut_version
510 dput
.dcut
.getoptions()
511 except FakeSystemExit
:
513 expected_output
= textwrap
.dedent("""\
514 D: {progname} {version}
516 progname
=expected_progname
,
517 version
=expected_version
)
518 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
520 def test_calls_getopt_with_expected_args(self
):
521 """ Should call `getopt` with expected arguments. """
523 dput
.dcut
.getoptions()
524 except FakeSystemExit
:
526 dputhelper
.getopt
.assert_called_with(
527 self
.sys_argv
[1:], mock
.ANY
, mock
.ANY
)
529 def test_emits_debug_message_for_each_option(self
):
530 """ Should emit a debug message for each option processed. """
531 sys
.argv
.insert(1, "--debug")
533 dput
.dcut
.getoptions()
534 except FakeSystemExit
:
536 expected_output_lines
= [
537 "D: processing arg \"{opt}\", option \"{arg}\"".format(
538 opt
=option
, arg
=option_argument
)
539 for (option
, option_argument
) in self
.getopt_args
]
540 expected_output
= "\n".join(expected_output_lines
)
541 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
543 def test_emits_expected_message(self
):
544 """ Should emit message with expected content. """
546 dput
.dcut
.getoptions()
547 except FakeSystemExit
:
549 if hasattr(self
, 'expected_stdout_output'):
550 self
.assertIn(self
.expected_stdout_output
, sys
.stdout
.getvalue())
551 if hasattr(self
, 'expected_stderr_output'):
552 self
.assertIn(self
.expected_stderr_output
, sys
.stderr
.getvalue())
554 def test_calls_sys_exit_with_expected_exit_status(self
):
555 """ Should call `sys.exit` with expected exit status. """
556 if not hasattr(self
, 'expected_exit_status'):
557 dput
.dcut
.getoptions()
559 with testtools
.ExpectedException(FakeSystemExit
):
560 dput
.dcut
.getoptions()
561 sys
.exit
.assert_called_with(self
.expected_exit_status
)
563 def test_returns_expected_values(self
):
564 """ Should return expected values. """
565 if not hasattr(self
, 'expected_result'):
566 self
.skipTest("No return result expected")
567 result
= dput
.dcut
.getoptions()
568 self
.assertEqual(self
.expected_result
, result
)
571 class getoptions_DetermineHostTestCase(
572 testscenarios
.WithScenarios
,
573 getoptions_TestCase
):
574 """ Test cases for `getoptions` function, determine host name. """
577 ('domain-from-mailname-file', {
578 'mailname_fake_file': StringIO("consecteur.example.org"),
582 default_options
= getattr(
583 getoptions_ParseCommandLineTestCase
, 'default_options')
585 command_scenarios
= [
586 ('no-opts no-args', {
589 'expected_options': {
593 ('no-opts command-first-arg', {
595 'getopt_args': ["cancel"],
596 'expected_options': {
599 'expected_arguments': ["cancel"],
601 ('no-opts host-first-arg', {
603 'getopt_args': ["quux.example.com", "cancel"],
604 'expected_options': {
605 'host': "quux.example.com",
607 'expected_arguments': ["cancel"],
608 'expected_debug_output': textwrap
.dedent("""\
609 D: first argument "quux.example.com" treated as host
612 ('option-host host-first-arg', {
613 'getopt_opts': [("--host", "quux.example.com")],
614 'getopt_args': ["decoy.example.net", "cancel"],
615 'expected_options': {
616 'host': "quux.example.com",
618 'expected_arguments': ["decoy.example.net", "cancel"],
622 scenarios
= testscenarios
.multiply_scenarios(
623 system_scenarios
, command_scenarios
)
625 def test_emits_expected_debug_message(self
):
626 """ Should emit the expected debug message. """
627 if not hasattr(self
, 'expected_debug_output'):
628 self
.expected_debug_output
= ""
629 self
.getopt_opts
= list(
630 self
.getopt_opts
+ [("--debug", None)])
631 dput
.dcut
.getoptions()
632 self
.assertIn(self
.expected_debug_output
, sys
.stdout
.getvalue())
634 def test_returns_expected_values(self
):
635 """ Should return expected values. """
636 if not hasattr(self
, 'expected_result'):
637 self
.skipTest("No return result expected")
638 (options
, arguments
) = dput
.dcut
.getoptions()
639 self
.assertEqual(self
.expected_options
['host'], options
['host'])
640 self
.assertEqual(self
.expected_arguments
, arguments
)
643 class parse_queuecommands_TestCase(testtools
.TestCase
):
644 """ Base for test cases for `parse_queuecommands` function. """
646 scenarios
= NotImplemented
649 """ Set up test fixtures. """
650 super(parse_queuecommands_TestCase
, self
).setUp()
651 patch_system_interfaces(self
)
655 def set_test_args(self
):
656 """ Set the arguments for the test call to the function. """
660 self
.test_args
= dict(
661 arguments
=getattr(self
, 'arguments', []),
662 options
=getattr(self
, 'options', default_options
),
667 class parse_queuecommands_SuccessTestCase(
668 testscenarios
.WithScenarios
,
669 parse_queuecommands_TestCase
):
670 """ Success test cases for `parse_queuecommands` function. """
674 'arguments': ["rm", "lorem.deb"],
675 'expected_commands': [
676 "rm --searchdirs lorem.deb",
679 ('one-command-rm nosearchdirs', {
680 'arguments': ["rm", "--nosearchdirs", "lorem.deb"],
681 'expected_commands': [
685 ('one-command-cancel', {
686 'arguments': ["cancel", "lorem.deb"],
687 'expected_commands': [
691 ('one-command-cancel nosearchdirs', {
692 'arguments': ["cancel", "--nosearchdirs", "lorem.deb"],
693 'expected_commands': [
694 "cancel --nosearchdirs lorem.deb",
697 ('one-command-reschedule', {
698 'arguments': ["reschedule", "lorem.deb"],
699 'expected_commands': [
700 "reschedule lorem.deb",
703 ('one-command-reschedule nosearchdirs', {
704 'arguments': ["reschedule", "--nosearchdirs", "lorem.deb"],
705 'expected_commands': [
706 "reschedule --nosearchdirs lorem.deb",
709 ('three-commands comma-separated', {
712 "cancel", "bar", ",",
713 "reschedule", "baz"],
714 'expected_commands': [
715 "rm --searchdirs foo ",
720 ('three-commands semicolon-separated', {
723 "cancel", "bar", ";",
724 "reschedule", "baz"],
725 'expected_commands': [
726 "rm --searchdirs foo ",
733 def test_emits_debug_message_for_each_command(self
):
734 """ Should emit a debug message for each command. """
735 self
.test_args
['options'] = dict(self
.test_args
['options'])
736 self
.test_args
['options']['debug'] = True
737 dput
.dcut
.parse_queuecommands(**self
.test_args
)
738 expected_output
= "\n".join(
739 "D: Successfully parsed command \"{command}\"".format(
741 for command
in self
.expected_commands
)
742 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
744 def test_returns_expected_commands(self
):
745 """ Should return expected commands value. """
746 result
= dput
.dcut
.parse_queuecommands(**self
.test_args
)
747 self
.assertEqual(self
.expected_commands
, result
)
750 class parse_queuecommands_ErrorTestCase(
751 testscenarios
.WithScenarios
,
752 parse_queuecommands_TestCase
):
753 """ Error test cases for `parse_queuecommands` function. """
758 'expected_debug_output': textwrap
.dedent("""\
759 Error: no arguments given, see dcut -h
761 'expected_exit_status': EXIT_STATUS_FAILURE
,
763 ('first-command-bogus', {
764 'arguments': ["b0gUs", "spam", "eggs"],
765 'expected_debug_output': textwrap
.dedent("""\
766 Error: Could not parse commands at "b0gUs"
768 'expected_exit_status': EXIT_STATUS_FAILURE
,
770 ('third-command-bogus', {
771 'arguments': ["rm", "foo", ",", "cancel", "bar", ",", "b0gUs"],
772 'expected_debug_output': textwrap
.dedent("""\
773 Error: Could not parse commands at "b0gUs"
775 'expected_exit_status': EXIT_STATUS_FAILURE
,
779 def test_emits_expected_error_message(self
):
780 """ Should emit expected error message. """
782 dput
.dcut
.parse_queuecommands(**self
.test_args
)
783 except FakeSystemExit
:
785 self
.assertIn(self
.expected_debug_output
, sys
.stderr
.getvalue())
787 def test_calls_sys_exit_with_exit_status(self
):
788 """ Should call `sys.exit` with expected exit status. """
789 with testtools
.ExpectedException(FakeSystemExit
):
790 dput
.dcut
.parse_queuecommands(**self
.test_args
)
791 sys
.exit
.assert_called_with(self
.expected_exit_status
)
794 class create_commands_TestCase(
796 """ Test cases for `create_commands` function. """
799 """ Set up test fixtures. """
800 super(create_commands_TestCase
, self
).setUp()
801 patch_system_interfaces(self
)
803 self
.changes_file_scenarios
= make_changes_file_scenarios()
804 set_changes_file_scenario(self
, 'no-format')
805 setup_file_double_behaviour(self
)
807 self
.set_expected_commands()
811 test_dput_main
.patch_parse_changes(self
)
812 dput
.dput
.parse_changes
.return_value
= self
.changes_file_scenario
[
817 def set_options(self
):
818 """ Set the options mapping to pass to the function. """
821 'changes': self
.changes_file_double
.path
,
824 def set_test_args(self
):
825 """ Set the arguments for the test call to the function. """
826 self
.test_args
= dict(
827 options
=dict(self
.options
),
829 parse_changes
=dput
.dput
.parse_changes
,
832 def set_expected_commands(self
):
833 """ Set the expected commands for this test case. """
834 files_to_remove
= [os
.path
.basename(self
.changes_file_double
.path
)]
835 files_from_changes
= self
.changes_file_scenario
[
836 'expected_result']['files']
837 for line
in files_from_changes
.split("\n"):
838 files_to_remove
.append(line
.split(" ")[4])
839 self
.expected_commands
= [
840 "rm --searchdirs {path}".format(path
=path
)
841 for path
in files_to_remove
]
843 def test_emits_debug_message_for_changes_file(self
):
844 """ Should emit debug message for changes file. """
845 self
.options
['debug'] = True
847 dput
.dcut
.create_commands(**self
.test_args
)
848 expected_output
= textwrap
.dedent("""\
849 D: Parsing changes file ({path}) for files to remove
850 """).format(path
=self
.changes_file_double
.path
)
851 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
853 def test_emits_error_message_when_changes_file_open_error(self
):
854 """ Should emit error message when changes file raises error. """
855 self
.changes_file_double
.set_open_scenario('read_denied')
857 dput
.dcut
.create_commands(**self
.test_args
)
858 except FakeSystemExit
:
860 expected_output
= textwrap
.dedent("""\
861 Can't open changes file: {path}
862 """).format(path
=self
.changes_file_double
.path
)
863 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
865 def test_calls_sys_exit_when_changes_file_open_error(self
):
866 """ Should call `sys.exit` when changes file raises error. """
867 self
.changes_file_double
.set_open_scenario('read_denied')
868 with testtools
.ExpectedException(FakeSystemExit
):
869 dput
.dcut
.create_commands(**self
.test_args
)
870 sys
.exit
.assert_called_with(EXIT_STATUS_FAILURE
)
872 def test_returns_expected_result(self
):
873 """ Should return expected result. """
874 result
= dput
.dcut
.create_commands(**self
.test_args
)
875 self
.assertEqual(self
.expected_commands
, result
)
878 class write_commands_TestCase(
879 testscenarios
.WithScenarios
,
881 """ Test cases for `write_commands` function. """
884 'filetocreate': None,
888 ('default-path', {}),
890 'option_filetocreate': str("ipsum.commands"),
891 'expected_result': "ipsum.commands",
898 commands_scenarios
= [
906 'commands': ["foo", "bar", "baz"],
913 'option_keyid': "DEADBEEF",
917 scenarios
= testscenarios
.multiply_scenarios(
918 path_scenarios
, commands_scenarios
, keyid_scenarios
)
920 for (scenario_name
, scenario
) in scenarios
:
921 default_options
= getattr(
922 getoptions_ParseCommandLineTestCase
, 'default_options')
923 options
= dict(default_options
)
925 'uploader': str("Lorem Ipsum <flup@example.org>"),
927 scenario
['uploader_filename_part'] = str(
928 "Lorem_Ipsum__flup_example_org_")
929 if 'option_filetocreate' in scenario
:
930 options
['filetocreate'] = scenario
['option_filetocreate']
931 if 'option_keyid' in scenario
:
932 options
['keyid'] = scenario
['option_keyid']
933 scenario
['options'] = options
934 if 'tempdir' not in scenario
:
935 scenario
['tempdir'] = tempfile
.mktemp()
936 del scenario_name
, scenario
937 del default_options
, options
940 """ Set up test fixtures. """
941 super(write_commands_TestCase
, self
).setUp()
942 patch_system_interfaces(self
)
944 patch_os_getpid(self
)
945 os
.getpid
.return_value
= self
.getUniqueInteger()
947 self
.time_return_value
= self
.getUniqueInteger()
948 patch_time_time(self
, itertools
.repeat(self
.time_return_value
))
951 self
.set_commands_file_double()
952 setup_file_double_behaviour(self
)
953 self
.set_expected_result()
959 patch_subprocess_popen(self
)
960 patch_subprocess_check_call(self
)
961 self
.set_debsign_subprocess_double()
962 setup_subprocess_double_behaviour(self
)
964 def set_options(self
):
965 """ Set the options mapping to pass to the function. """
967 def set_test_args(self
):
968 """ Set the arguments for the test call to the function. """
969 self
.test_args
= dict(
970 commands
=list(self
.commands
),
971 options
=dict(self
.options
),
973 tempdir
=self
.tempdir
,
976 def make_commands_filename(self
):
977 """ Make the filename for the commands output file. """
978 expected_progname
= self
.progname
979 filename
= "{progname}.{uploadpart}.{time:d}.{pid:d}.commands".format(
980 progname
=expected_progname
,
981 uploadpart
=self
.uploader_filename_part
,
982 time
=self
.time_return_value
,
983 pid
=os
.getpid
.return_value
)
986 def set_commands_file_double(self
):
987 """ Set the commands file double for this test case. """
988 if self
.options
['filetocreate']:
989 path
= self
.options
['filetocreate']
991 output_filename
= self
.make_commands_filename()
993 path
= os
.path
.join(self
.tempdir
, output_filename
)
995 path
= output_filename
996 double
= FileDouble(path
)
997 double
.register_for_testcase(self
)
998 self
.commands_file_double
= double
1000 def set_expected_result(self
):
1001 """ Set the `expected_result` for this test case. """
1002 self
.expected_result
= self
.commands_file_double
.path
1004 def set_commands(self
):
1005 """ Set the commands to use for this test case. """
1006 if not hasattr(self
, 'commands'):
1009 def make_expected_content(self
):
1010 """ Make the expected content for the output file. """
1011 uploader_value
= self
.options
['uploader']
1013 commands_value
= "\n".join(
1014 " {command}".format(command
=command
)
1015 for command
in self
.commands
)
1017 commands_value
= " "
1018 commands_value
+= "\n"
1019 text
= textwrap
.dedent("""\
1020 Uploader: {uploader}
1023 """).format(uploader
=uploader_value
, commands
=commands_value
)
1026 def set_debsign_subprocess_double(self
):
1027 """ Set the ‘debsign’ subprocess double for this test case. """
1028 path
= "/usr/bin/debsign"
1029 argv
= [os
.path
.basename(path
), ARG_MORE
]
1030 double
= SubprocessDouble(path
, argv
)
1031 double
.register_for_testcase(self
)
1032 self
.debsign_subprocess_double
= double
1034 def make_expected_debsign_argv(self
):
1035 """ Make the expected command-line arguments for ‘debsign’. """
1038 str("-m{uploader}").format(uploader
=self
.options
['uploader']),
1040 if self
.options
['keyid']:
1042 "-k{keyid}".format(keyid
=self
.options
['keyid']))
1043 argv
.append(self
.commands_file_double
.path
)
1047 def test_returns_expected_file_path(self
):
1048 """ Should return expected file path. """
1049 result
= dput
.dcut
.write_commands(**self
.test_args
)
1050 self
.assertEqual(self
.expected_result
, result
)
1052 def test_output_file_has_expected_content(self
):
1053 """ Should have expected content in output file. """
1054 with mock
.patch
.object(
1055 self
.commands_file_double
.fake_file
, "close", autospec
=True):
1056 dput
.dcut
.write_commands(**self
.test_args
)
1057 expected_value
= self
.make_expected_content()
1059 expected_value
, self
.commands_file_double
.fake_file
.getvalue())
1061 def test_emits_debug_message_for_debsign(self
):
1062 """ Should emit debug message for ‘debsign’ command. """
1063 self
.options
['debug'] = True
1064 self
.test_args
['options'] = self
.options
1065 dput
.dcut
.write_commands(**self
.test_args
)
1066 debsign_argv
= self
.make_expected_debsign_argv()
1067 expected_output
= textwrap
.dedent("""\
1068 D: calling debsign: {argv}
1069 """).format(argv
=debsign_argv
)
1070 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1072 def test_calls_subprocess_check_call_with_expected_args(self
):
1073 """ Should call `subprocess.check_call` with expected args. """
1074 debsign_argv
= self
.make_expected_debsign_argv()
1075 expected_args
= [debsign_argv
]
1076 dput
.dcut
.write_commands(**self
.test_args
)
1077 subprocess
.check_call
.assert_called_with(*expected_args
)
1079 def test_emits_error_message_when_debsign_failure(self
):
1080 """ Should emit error message when ‘debsign’ command failure. """
1081 self
.debsign_subprocess_double
.set_subprocess_check_call_scenario(
1084 dput
.dcut
.write_commands(**self
.test_args
)
1085 except FakeSystemExit
:
1087 expected_output
= textwrap
.dedent("""\
1088 Error: debsign failed.
1090 self
.assertIn(expected_output
, sys
.stderr
.getvalue())
1092 def test_calls_sys_exit_when_debsign_failure(self
):
1093 """ Should call `sys.exit` when ‘debsign’ command failure. """
1094 self
.debsign_subprocess_double
.set_subprocess_check_call_scenario(
1096 with testtools
.ExpectedException(FakeSystemExit
):
1097 dput
.dcut
.write_commands(**self
.test_args
)
1098 sys
.exit
.assert_called_with(EXIT_STATUS_FAILURE
)
1101 class upload_TestCase(test_dput_main
.main_TestCase
):
1102 """ Base for test cases for `upload_stolen_from_dput_main` function. """
1104 function_to_test
= staticmethod(dput
.dcut
.upload_stolen_from_dput_main
)
1107 """ Set up test fixtures. """
1108 super(upload_TestCase
, self
).setUp()
1110 self
.set_cat_subprocess_double()
1111 patch_subprocess_call(self
)
1112 patch_tempfile_mkdtemp(self
)
1113 patch_os_rmdir(self
)
1115 patch_getoptions(self
)
1117 def set_cat_subprocess_double(self
):
1118 """ Set the ‘cat’ subprocess double for this test case. """
1120 argv
= [os
.path
.basename(path
), ARG_ANY
]
1121 double
= SubprocessDouble(path
, argv
)
1122 double
.register_for_testcase(self
)
1123 double
.set_subprocess_call_scenario('success')
1124 self
.cat_subprocess_double
= double
1126 def set_test_args(self
):
1127 """ Set the arguments for the test call to the function. """
1128 self
.test_args
= dict(
1129 host
=self
.test_host
,
1130 upload_methods
=self
.upload_methods
,
1131 config
=self
.runtime_config_parser
,
1134 files_to_upload
=self
.files_to_upload
,
1135 ftp_passive_mode
=False,
1138 if hasattr(self
, 'test_args_extra'):
1139 self
.test_args
.update(self
.test_args_extra
)
1141 def get_upload_method_func(self
):
1142 """ Get the specified upload method. """
1143 method_name
= self
.runtime_config_parser
.get(self
.test_host
, 'method')
1144 method_func
= self
.upload_methods
[method_name
]
1148 class upload_DebugMessageTestCase(upload_TestCase
):
1149 """ Test cases for `upload_stolen_from_dput_main` debug messages. """
1151 def test_emits_debug_message_for_discovered_methods(self
):
1152 """ Should emit debug message for discovered upload methods. """
1153 self
.test_args
['debug'] = True
1154 self
.function_to_test(**self
.test_args
)
1155 expected_output
= textwrap
.dedent("""\
1156 D: Default Method: {default_method}
1157 D: Host Method: {host_method}
1159 default_method
=self
.runtime_config_parser
.get(
1160 'DEFAULT', 'method'),
1161 host_method
=self
.runtime_config_parser
.get(
1162 self
.test_host
, 'method'))
1163 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1166 class upload_UnknownUploadMethodTestCase(
1167 testscenarios
.WithScenarios
,
1169 """ Test cases for `upload_stolen_from_dput_main`, unknown method. """
1172 ('bogus-default-method', {
1178 'expected_output': "Unknown upload method: b0gUs",
1179 'expected_exit_status': EXIT_STATUS_FAILURE
,
1181 ('bogus-host-method', {
1187 'expected_output': "Unknown upload method: b0gUs",
1188 'expected_exit_status': EXIT_STATUS_FAILURE
,
1192 def test_emits_error_message_when_unknown_method(self
):
1193 """ Should emit error message when unknown upload method. """
1195 self
.function_to_test(**self
.test_args
)
1196 except FakeSystemExit
:
1198 self
.assertIn(self
.expected_output
, sys
.stderr
.getvalue())
1200 def test_calls_sys_exit_when_unknown_method(self
):
1201 """ Should call `sys.exit` when unknown upload method. """
1202 with testtools
.ExpectedException(FakeSystemExit
):
1203 self
.function_to_test(**self
.test_args
)
1204 sys
.exit
.assert_called_with(self
.expected_exit_status
)
1207 class upload_DiscoverLoginTestCase(
1208 testscenarios
.WithScenarios
,
1210 """ Test cases for `upload_stolen_from_dput_main` discovery of login. """
1212 fallback_login_scenarios
= [
1213 ('login-from-environ', {
1215 'USER': "login-from-environ",
1217 'expected_fallback_login': "login-from-environ",
1218 'expected_system_uid_debug_message': "",
1220 ('login-from-pwd', {
1221 'os_getuid_return_value': 42,
1222 'pwd_getpwuid_return_value': PasswdEntry(
1223 *(["login-from-pwd"] + [object()] * 6)),
1224 'expected_fallback_login': "login-from-pwd",
1225 'expected_system_uid_debug_message': "D: User-ID: 42",
1229 config_login_scenarios
= [
1230 ('config-default-login', {
1233 'login': "login-from-config-default",
1236 'expected_login': "login-from-config-default",
1237 'expected_output_template':
1238 "D: Login to use: {login}",
1240 ('config-host-login', {
1243 'login': "login-from-config-host",
1246 'expected_login': "login-from-config-host",
1247 'expected_output_template':
1248 "D: Login to use: {login}",
1250 ('config-default-login sentinel', {
1253 'login': "username",
1256 'expected_output_template': (
1257 "D: Neither host {host} nor default login used."
1260 ('config-host-login sentinel', {
1263 'login': "username",
1266 'expected_output_template': (
1267 "D: Neither host {host} nor default login used."
1272 scenarios
= testscenarios
.multiply_scenarios(
1273 fallback_login_scenarios
, config_login_scenarios
)
1274 for (scenario_name
, scenario
) in scenarios
:
1275 if 'expected_login' not in scenario
:
1276 scenario
['expected_login'] = scenario
['expected_fallback_login']
1277 del scenario_name
, scenario
1279 def test_emits_debug_message_for_system_uid(self
):
1280 """ Should emit a debug message for the system UID. """
1281 if self
.expected_login
!= self
.expected_fallback_login
:
1282 self
.skipTest("No fallback in this scenario")
1283 self
.test_args
['debug'] = True
1284 self
.function_to_test(**self
.test_args
)
1285 expected_output
= self
.expected_system_uid_debug_message
.format(
1286 uid
=self
.os_getuid_return_value
)
1287 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1289 def test_emits_debug_message_for_discovered_login(self
):
1290 """ Should emit a debug message for the discovered login. """
1291 self
.test_args
['debug'] = True
1292 self
.function_to_test(**self
.test_args
)
1293 expected_output
= self
.expected_output_template
.format(
1294 login
=self
.expected_login
, host
=self
.test_host
)
1295 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1297 def test_calls_upload_method_with_expected_login(self
):
1298 """ Should call upload method function with expected login arg. """
1299 upload_method_func
= get_upload_method_func(self
)
1300 self
.function_to_test(**self
.test_args
)
1301 upload_method_func
.assert_called_with(
1302 mock
.ANY
, self
.expected_login
,
1303 mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
)
1306 class upload_SimulateTestCase(
1307 testscenarios
.WithScenarios
,
1309 """ Test cases for `upload_stolen_from_dput_main`, ‘simulate’ option. """
1313 'config_default_login': "login-from-config-default",
1314 'test_args_extra': {
1318 ('simulate three-files', {
1319 'config_default_login': "login-from-config-default",
1320 'test_args_extra': {
1323 'files_to_upload': [tempfile
.mktemp() for __
in range(3)],
1327 def test_omits_upload_method(self
):
1328 """ Should omit call to upload method function. """
1329 upload_method_func
= get_upload_method_func(self
)
1330 self
.function_to_test(**self
.test_args
)
1331 self
.assertFalse(upload_method_func
.called
)
1333 def test_emits_message_for_each_file_to_upload(self
):
1334 """ Should emit a message for each file to upload. """
1335 self
.function_to_test(**self
.test_args
)
1336 method
= self
.runtime_config_parser
.get(self
.test_host
, 'method')
1337 fqdn
= self
.runtime_config_parser
.get(self
.test_host
, 'fqdn')
1338 incoming
= self
.runtime_config_parser
.get(self
.test_host
, 'incoming')
1339 expected_output
= "\n".join(
1340 "Uploading with {method}: {path} to {fqdn}:{incoming}".format(
1341 method
=method
, path
=path
,
1342 fqdn
=fqdn
, incoming
=incoming
)
1343 for path
in self
.files_to_upload
)
1344 self
.assertIn(expected_output
, sys
.stderr
.getvalue())
1346 def test_calls_cat_for_each_file_to_upload(self
):
1347 """ Should call ‘cat’ for each file to upload. """
1348 self
.function_to_test(**self
.test_args
)
1349 for path
in self
.files_to_upload
:
1350 expected_call
= mock
.call(
1351 "cat {path}".format(path
=path
),
1354 subprocess
.call
.mock_calls
,
1355 testtools
.matchers
.Contains(expected_call
))
1358 class upload_UploadMethodTestCase(
1359 testscenarios
.WithScenarios
,
1361 """ Test cases for `upload_stolen_from_dput_main`, invoking method. """
1363 method_scenarios
= [
1365 'config_method': "local",
1366 'config_progress_indicator': 23,
1368 "localhost", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1372 'config_method': "ftp",
1373 'config_fqdn': "foo.example.com",
1374 'config_passive_ftp': False,
1375 'config_progress_indicator': 23,
1377 "foo.example.com", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1379 'expected_stdout_output': "",
1381 ('method-ftp port-custom', {
1382 'config_method': "ftp",
1383 'config_fqdn': "foo.example.com:42",
1384 'config_passive_ftp': False,
1385 'config_progress_indicator': 23,
1387 "foo.example.com:42",
1388 mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1390 'expected_stdout_output': "",
1392 ('method-ftp config-passive-mode', {
1393 'config_method': "ftp",
1394 'config_fqdn': "foo.example.com",
1395 'config_passive_ftp': True,
1396 'config_progress_indicator': 23,
1398 "foo.example.com", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1400 'expected_stdout_output': "",
1402 ('method-ftp config-passive-mode arg-ftp-active-mode', {
1403 'config_method': "ftp",
1404 'config_fqdn': "foo.example.com",
1405 'config_passive_ftp': True,
1406 'config_progress_indicator': 23,
1407 'test_args_extra': {
1408 'ftp_passive_mode': False,
1411 "foo.example.com", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1413 'expected_stdout_output': "D: Using active ftp",
1415 ('method-ftp arg-ftp-passive-mode', {
1416 'config_method': "ftp",
1417 'config_fqdn': "foo.example.com",
1418 'config_progress_indicator': 23,
1419 'test_args_extra': {
1420 'ftp_passive_mode': True,
1423 "foo.example.com", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1425 'expected_stdout_output': "D: Using passive ftp",
1427 ('method-ftp config-passive-mode arg-ftp-passive-mode', {
1428 'config_method': "ftp",
1429 'config_fqdn': "foo.example.com",
1430 'config_passive_ftp': True,
1431 'config_progress_indicator': 23,
1432 'test_args_extra': {
1433 'ftp_passive_mode': True,
1436 "foo.example.com", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1438 'expected_stdout_output': "D: Using passive ftp",
1441 'config_method': "scp",
1442 'config_fqdn': "foo.example.com",
1444 "foo.example.com", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1446 'expected_stdout_output': "",
1448 ('method-scp scp-compress', {
1449 'config_method': "scp",
1450 'config_fqdn': "foo.example.com",
1453 'scp_compress': "True",
1454 'ssh_config_options': "spam eggs beans",
1458 "foo.example.com", mock
.ANY
, mock
.ANY
, mock
.ANY
, mock
.ANY
,
1459 True, ["spam eggs beans"]),
1460 'expected_stdout_output': "D: Setting compression for scp",
1466 'config_default_login': "login-from-config-default",
1470 commands_scenarios
= [
1471 ('commands-from-changes', {
1472 'getoptions_args': ["foo", "bar", "baz"],
1473 'getoptions_opts': {
1474 'filetocreate': None,
1475 'filetoupload': tempfile
.mktemp() + "commands",
1478 ('commands-from-changes', {
1479 'getoptions_args': ["foo", "bar", "baz"],
1480 'getoptions_opts': {
1481 'filetocreate': None,
1482 'filetoupload': None,
1483 'changes': tempfile
.mktemp(),
1486 ('commands-from-arguments', {
1487 'getoptions_args': ["foo", "bar", "baz"],
1488 'getoptions_opts': {
1489 'filetocreate': None,
1490 'filetoupload': None,
1498 'files_to_remove': [],
1501 'files_to_remove': [
1502 tempfile
.mktemp() for __
in range(3)],
1506 scenarios
= testscenarios
.multiply_scenarios(
1507 method_scenarios
, login_scenarios
,
1508 commands_scenarios
, files_scenarios
)
1510 def test_emits_expected_debug_message(self
):
1511 """ Should emit expected debug message. """
1512 self
.test_args
['debug'] = True
1513 self
.function_to_test(**self
.test_args
)
1514 if hasattr(self
, 'expected_stdout_output'):
1515 self
.assertIn(self
.expected_stdout_output
, sys
.stdout
.getvalue())
1517 def test_calls_upload_method_with_expected_args(self
):
1518 """ Should call upload method function with expected args. """
1519 upload_method_func
= get_upload_method_func(self
)
1520 self
.function_to_test(**self
.test_args
)
1521 upload_method_func
.assert_called_with(*self
.expected_args
)
1524 class dcut_TestCase(testtools
.TestCase
):
1525 """ Base for test cases for `dput` function. """
1528 """ Set up test fixtures. """
1529 super(dcut_TestCase
, self
).setUp()
1530 patch_system_interfaces(self
)
1532 patch_tempfile_mkdtemp(self
)
1533 patch_os_unlink(self
)
1534 patch_os_rmdir(self
)
1535 patch_shutil_rmtree(self
)
1539 getattr(self
, 'config_scenario_name', 'exist-simple'))
1540 test_dput_main
.patch_runtime_config_options(self
)
1542 self
.set_test_args()
1544 patch_getoptions(self
)
1545 test_dput_main
.patch_parse_changes(self
)
1546 test_dput_main
.patch_read_configs(self
)
1547 test_dput_main
.set_upload_methods(self
)
1548 test_dput_main
.patch_import_upload_functions(self
)
1550 self
.patch_parse_queuecommands()
1551 self
.patch_create_commands()
1552 self
.patch_write_commands()
1553 self
.patch_upload_stolen_from_dput_main()
1555 def set_test_args(self
):
1556 """ Set the arguments for the test call to the function. """
1557 self
.test_args
= dict()
1559 def patch_parse_queuecommands(self
):
1560 """ Patch the `parse_queuecommands` function for this test case. """
1561 func_patcher
= mock
.patch
.object(
1562 dput
.dcut
, "parse_queuecommands", autospec
=True)
1563 func_patcher
.start()
1564 self
.addCleanup(func_patcher
.stop
)
1566 def patch_create_commands(self
):
1567 """ Patch the `create_commands` function for this test case. """
1568 func_patcher
= mock
.patch
.object(
1569 dput
.dcut
, "create_commands", autospec
=True)
1570 func_patcher
.start()
1571 self
.addCleanup(func_patcher
.stop
)
1573 def patch_write_commands(self
):
1574 """ Patch the `write_commands` function for this test case. """
1575 func_patcher
= mock
.patch
.object(
1576 dput
.dcut
, "write_commands", autospec
=True)
1577 func_patcher
.start()
1578 self
.addCleanup(func_patcher
.stop
)
1580 def patch_upload_stolen_from_dput_main(self
):
1581 """ Patch `upload_stolen_from_dput_main` for this test case. """
1582 func_patcher
= mock
.patch
.object(
1583 dput
.dcut
, "upload_stolen_from_dput_main", autospec
=True)
1584 func_patcher
.start()
1585 self
.addCleanup(func_patcher
.stop
)
1588 class dcut_DebugMessageTestCase(dcut_TestCase
):
1589 """ Test cases for `dcut` debug messages. """
1591 def test_emits_debug_message_for_read_configs(self
):
1592 """ Should emit debug message for `read_configs` call. """
1593 self
.getoptions_opts
['debug'] = True
1594 dput
.dcut
.dcut(**self
.test_args
)
1595 expected_output
= textwrap
.dedent("""\
1596 D: calling dput.read_configs
1598 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1601 class dcut_ConfigFileTestCase(
1602 testscenarios
.WithScenarios
,
1604 """ Test cases for `main` specification of configuration file. """
1608 'expected_args': (None, mock
.ANY
),
1610 ('config-from-command-line', {
1611 'getoptions_opts': {
1612 'config': "lorem.conf",
1614 'expected_args': ("lorem.conf", mock
.ANY
),
1618 def test_calls_read_configs_with_expected_args(self
):
1619 """ Should call `read_configs` with expected arguments. """
1620 dput
.dcut
.dcut(**self
.test_args
)
1621 dput
.dput
.read_configs
.assert_called_with(*self
.expected_args
)
1624 class dcut_OptionsErrorTestCase(
1625 testscenarios
.WithScenarios
,
1627 """ Test cases for `dcut` function, startup options cause error. """
1630 ('no-host-discovered', {
1631 'config_default_default_host_main': None,
1632 'getoptions_opts': {
1635 'expected_output': (
1636 "Error: No host specified"
1637 " and no default found in config"),
1638 'expected_exit_status': EXIT_STATUS_FAILURE
,
1640 ('host-not-in-config', {
1641 'config_scenario_name': "exist-minimal",
1642 'expected_output': "No host foo found in config",
1643 'expected_exit_status': EXIT_STATUS_FAILURE
,
1646 'config_allow_dcut': False,
1647 'expected_output': (
1648 "Error: dcut is not supported for this upload queue."),
1649 'expected_exit_status': EXIT_STATUS_FAILURE
,
1651 ('filetoupload arguments', {
1652 'getoptions_opts': {
1653 'filetoupload': tempfile
.mktemp() + ".commands",
1655 'getoptions_args': ["lorem", "ipsum", "dolor", "sit", "amet"],
1656 'expected_output': (
1657 "Error: cannot take commands"
1658 " when uploading existing file"),
1659 'expected_exit_status': EXIT_STATUS_FAILURE
,
1663 def test_emits_expected_error_message(self
):
1664 """ Should emit expected error message. """
1666 dput
.dcut
.dcut(**self
.test_args
)
1667 except FakeSystemExit
:
1669 self
.assertIn(self
.expected_output
, sys
.stdout
.getvalue())
1671 def test_calls_sys_exit_with_failure_exit_status(self
):
1672 """ Should call `sys.exit` with failure exit status. """
1673 with testtools
.ExpectedException(FakeSystemExit
):
1674 dput
.dcut
.dcut(**self
.test_args
)
1675 sys
.exit
.assert_called_with(self
.expected_exit_status
)
1678 class dcut_NamedHostTestCase(
1679 testscenarios
.WithScenarios
,
1681 """ Test cases for `dcut` function, named host processing. """
1684 ('host-from-command-line', {
1685 'config_scenario_name': "exist-simple-host-three",
1686 'config_default_default_host_main': "quux",
1687 'getoptions_opts': {
1690 'expected_host': "bar",
1691 'expected_debug_output': "",
1693 ('host-from-config-default', {
1694 'config_scenario_name': "exist-simple-host-three",
1695 'config_default_default_host_main': "bar",
1696 'getoptions_opts': {
1699 'expected_host': "bar",
1700 'expected_debug_output': textwrap
.dedent("""\
1701 D: Using host "bar" (default_host_main)
1704 ('host-from-hardcoded-default', {
1705 'config_scenario_name': "exist-default-distribution-only",
1706 'config_default_default_host_main': "",
1707 'getoptions_opts': {
1710 'expected_host': "ftp-master",
1711 'expected_debug_output': textwrap
.dedent("""\
1712 D: Using host "" (default_host_main)
1713 D: Using host "ftp-master" (hardcoded)
1718 def test_emits_debug_message_for_discovered_host(self
):
1719 """ Should emit debug message for discovered host values. """
1720 self
.getoptions_opts
['debug'] = True
1721 dput
.dcut
.dcut(**self
.test_args
)
1722 self
.assertIn(self
.expected_debug_output
, sys
.stdout
.getvalue())
1724 def test_calls_write_commands_with_expected_host_option(self
):
1725 """ Should call `write_commands` with expected `host` option. """
1726 dput
.dcut
.dcut(**self
.test_args
)
1727 self
.assertEqual(1, len(dput
.dcut
.write_commands
.mock_calls
))
1728 (__
, call_args
, call_kwargs
) = dput
.dcut
.write_commands
.mock_calls
[0]
1729 (__
, options
, __
, __
) = call_args
1730 self
.assertEqual(self
.expected_host
, options
['host'])
1733 class dcut_FileToUploadBadNameTestCase(
1734 testscenarios
.WithScenarios
,
1736 """ Test cases for `dcut` function, file to upload with bad name. """
1740 'getoptions_args': [],
1741 'getoptions_opts': {
1742 'filetoupload': tempfile
.mktemp(),
1744 'expected_output': (
1745 "Error: I'm insisting on the .commands extension"),
1749 def test_emits_error_message_for_bad_filename(self
):
1750 """ Should emit error message for bad filename. """
1751 dput
.dcut
.dcut(**self
.test_args
)
1752 self
.assertIn(self
.expected_output
, sys
.stdout
.getvalue())
1755 class dcut_ParseChangesTestCase(
1756 testscenarios
.WithScenarios
,
1758 """ Test cases for `dcut` function, parse upload control file. """
1761 ('changes-file no-filetoupload', {
1762 'getoptions_opts': {
1763 'filetoupload': None,
1764 'changes': tempfile
.mktemp(),
1767 ('changes-file no-filetoupload no-filetocreate', {
1768 'getoptions_opts': {
1769 'filetoupload': None,
1770 'filetocreate': None,
1771 'changes': tempfile
.mktemp(),
1776 def test_calls_create_commands_with_expected_args(self
):
1777 """ Should call `create_commands` with expected args. """
1778 dput
.dcut
.dcut(**self
.test_args
)
1779 (expected_options
, __
) = dput
.dcut
.getoptions()
1780 expected_config
= self
.runtime_config_parser
1781 expected_parse_changes
= dput
.dput
.parse_changes
1782 dput
.dcut
.create_commands
.assert_called_with(
1783 expected_options
, expected_config
, expected_parse_changes
)
1785 def test_calls_write_commands_with_expected_args(self
):
1786 """ Should call `write_commands` with expected args. """
1787 expected_commands
= object()
1788 dput
.dcut
.create_commands
.return_value
= expected_commands
1789 dput
.dcut
.dcut(**self
.test_args
)
1790 (expected_options
, __
) = dput
.dcut
.getoptions()
1791 expected_config
= self
.runtime_config_parser
1792 expected_tempdir
= self
.tempfile_mkdtemp_file_double
.path
1793 dput
.dcut
.write_commands
.assert_called_with(
1794 expected_commands
, expected_options
, expected_config
,
1798 class dcut_ParseQueueCommandsTestCase(
1799 testscenarios
.WithScenarios
,
1801 """ Test cases for `dcut` function, parse commands from arguments. """
1804 ('no-changes-file no-filetoupload', {
1805 'getoptions_opts': {
1806 'filetoupload': None,
1812 def test_calls_parse_queuecommands_with_expected_args(self
):
1813 """ Should call `parse_queuecommands` with expected args. """
1814 dput
.dcut
.dcut(**self
.test_args
)
1815 (expected_options
, expected_arguments
) = dput
.dcut
.getoptions()
1816 expected_config
= self
.runtime_config_parser
1817 dput
.dcut
.parse_queuecommands
.assert_called_with(
1818 expected_arguments
, expected_options
, expected_config
)
1820 def test_calls_write_commands_with_expected_args(self
):
1821 """ Should call `write_commands` with expected args. """
1822 expected_commands
= object()
1823 dput
.dcut
.parse_queuecommands
.return_value
= expected_commands
1824 dput
.dcut
.dcut(**self
.test_args
)
1825 (expected_options
, __
) = dput
.dcut
.getoptions()
1826 expected_config
= self
.runtime_config_parser
1827 expected_tempdir
= self
.tempfile_mkdtemp_file_double
.path
1828 dput
.dcut
.write_commands
.assert_called_with(
1829 expected_commands
, expected_options
, expected_config
,
1833 class dcut_CleanupTestCase(
1834 testscenarios
.WithScenarios
,
1836 """ Test cases for `dcut` function, cleanup from exception. """
1838 commands_scenarios
= [
1839 ('commands-from-arguments', {
1840 'getoptions_args': ["foo", "bar", "baz"],
1841 'getoptions_opts': {
1842 'filetocreate': None,
1843 'filetoupload': None,
1849 files_scenarios
= upload_UploadMethodTestCase
.files_scenarios
1851 scenarios
= testscenarios
.multiply_scenarios(
1852 commands_scenarios
, files_scenarios
)
1855 """ Set up test fixtures. """
1856 super(dcut_CleanupTestCase
, self
).setUp()
1858 upload_method_func
= get_upload_method_func(self
)
1859 self
.upload_error
= RuntimeError("Bad stuff happened")
1860 upload_method_func
.side_effect
= self
.upload_error
1862 def test_removes_temporary_directory_when_upload_raises_exception(self
):
1863 """ Should remove directory `tempdir` when exception raised. """
1865 dput
.dcut
.dcut(**self
.test_args
)
1866 except self
.upload_error
.__class
__:
1868 shutil
.rmtree
.assert_called_with(
1869 self
.tempfile_mkdtemp_file_double
.path
)
1872 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
1874 # This is free software: you may copy, modify, and/or distribute this work
1875 # under the terms of the GNU General Public License as published by the
1876 # Free Software Foundation; version 3 of that license or any later version.
1877 # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details.
1884 # vim: fileencoding=utf-8 filetype=python :