1 # -*- coding: utf-8; -*-
3 # test/test_changesfile.py
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 Debian upload control (‘*.changes’) files. """
12 from __future__
import (absolute_import
, unicode_literals
)
27 from dput
.helper
import dputhelper
34 get_file_doubles_from_fake_file_scenarios
,
35 make_fake_file_scenarios
,
39 patch_system_interfaces
,
40 setup_file_double_behaviour
,
42 from .test_configfile
import (
43 patch_runtime_config_options
,
51 """ A mapping to stand in for the `dict` of an `email.message.Message`. """
53 def __init__(self
, *args
, **kwargs
):
55 self
._message
= kwargs
.pop('_message')
57 raise TypeError("no ‘_message’ specified for this mapping")
58 super(_FieldsMapping
, self
).__init
__(*args
, **kwargs
)
60 def __len__(self
, *args
, **kwargs
):
61 return self
._message
.__len
__(*args
, **kwargs
)
63 def __contains__(self
, *args
, **kwargs
):
64 return self
._message
.__contains
__(*args
, **kwargs
)
66 def __getitem__(self
, *args
, **kwargs
):
67 return self
._message
.__getitem
__(*args
, **kwargs
)
69 def __setitem__(self
, *args
, **kwargs
):
70 self
._message
.__setitem
__(*args
, **kwargs
)
72 def __delitem__(self
, *args
, **kwargs
):
73 self
._message
.__delitem
__(*args
, **kwargs
)
75 # Deprecated method for Python 2 compatibility.
76 def has_key(self
, *args
, **kwargs
):
77 return self
._message
.has_key(*args
, **kwargs
) # noqa: W601
79 def keys(self
, *args
, **kwargs
):
80 return self
._message
.keys(*args
, **kwargs
)
82 def values(self
, *args
, **kwargs
):
83 return self
._message
.values(*args
, **kwargs
)
85 def items(self
, *args
, **kwargs
):
86 return self
._message
.items(*args
, **kwargs
)
88 def get(self
, *args
, **kwargs
):
89 return self
._message
.get(*args
, **kwargs
)
92 class FakeMessage(email
.message
.Message
, object):
93 """ A fake RFC 2822 message that mocks the obsolete `rfc822.Message`. """
95 def __init__(self
, *args
, **kwargs
):
96 super(FakeMessage
, self
).__init
__(*args
, **kwargs
)
97 self
.dict = _FieldsMapping(_message
=self
)
100 def make_fake_message(fields
):
101 """ Make a fake message instance. """
102 message
= FakeMessage()
103 for (name
, value
) in fields
.items():
104 message
.add_header(name
, value
)
108 def make_files_field_value(params_by_name
):
109 """ Make a value for “Files” field of a changes document. """
112 for (file_name
, params
) in params_by_name
.items())
116 def make_upload_files_params(checksums_by_file_name
, sizes_by_file_name
):
117 """ Make a mapping of upload parameters for files. """
120 checksums_by_file_name
[file_name
],
121 str(sizes_by_file_name
[file_name
]),
122 "foo", "bar", file_name
]
123 for file_name
in checksums_by_file_name
}
124 return params_by_name
127 def make_changes_document(fields
, upload_params_by_name
=None):
128 """ Make a changes document from field values.
130 :param fields: Sequence of (name, value) tuples for fields.
131 :param upload_params_by_name: Mapping from filename to upload
132 parameters for each file.
133 :return: The changes document as an RFC 822 formatted text.
136 document_fields
= fields
.copy()
137 if upload_params_by_name
is not None:
138 files_field_text
= make_files_field_value(upload_params_by_name
)
139 document_fields
.update({'files': files_field_text
})
140 document
= make_fake_message(document_fields
)
145 def make_changes_file_scenarios():
146 """ Make fake Debian upload control (‘*.changes’) scenarios. """
147 file_path
= make_changes_file_path()
149 fake_file_empty
= StringIO()
150 fake_file_no_format
= StringIO(textwrap
.dedent("""\
154 Lorem ipsum dolor sit amet
156 fake_file_with_signature
= StringIO(textwrap
.dedent("""\
157 -----BEGIN PGP SIGNED MESSAGE-----
163 Lorem ipsum dolor sit amet
165 -----BEGIN PGP SIGNATURE-----
167 Comment: Proin ac massa at orci sagittis fermentum.
169 gibberishgibberishgibberishgibberishgibberishgibberish
170 gibberishgibberishgibberishgibberishgibberishgibberish
171 gibberishgibberishgibberishgibberishgibberishgibberish
172 -----END PGP SIGNATURE-----
174 fake_file_with_format
= StringIO(textwrap
.dedent("""\
177 Lorem ipsum dolor sit amet
179 fake_file_invalid
= StringIO(textwrap
.dedent("""\
187 'file_double': FileDouble(
189 fake_file
=fake_file_no_format
),
190 'expected_result': make_changes_document({
191 'files': "Lorem ipsum dolor sit amet",
194 ('with-pgp-signature', {
195 'file_double': FileDouble(
197 fake_file
=fake_file_with_signature
),
198 'expected_result': make_changes_document({
199 'files': "Lorem ipsum dolor sit amet",
203 'file_double': FileDouble(
205 fake_file
=fake_file_with_format
),
206 'expected_result': make_changes_document({
207 'files': "Lorem ipsum dolor sit amet",
211 'file_double': FileDouble(
212 path
=file_path
, fake_file
=fake_file_empty
),
213 'expected_error': KeyError,
216 'file_double': FileDouble(
218 fake_file
=fake_file_invalid
),
219 'expected_error': FakeSystemExit
,
223 for (scenario_name
, scenario
) in scenarios
:
224 scenario
['changes_file_scenario_name'] = scenario_name
229 def set_fake_upload_file_paths(testcase
):
230 """ Set the fake upload file paths. """
231 testcase
.fake_upload_file_paths
= [
233 os
.path
.dirname(testcase
.changes_file_double
.path
),
234 os
.path
.basename(tempfile
.mktemp()))
237 required_suffixes
= [".dsc", ".tar.xz"]
238 suffixes
= required_suffixes
+ getattr(
239 testcase
, 'additional_file_suffixes', [])
240 file_path_base
= testcase
.fake_upload_file_paths
.pop()
241 for suffix
in suffixes
:
242 file_path
= file_path_base
+ suffix
243 testcase
.fake_upload_file_paths
.insert(0, file_path
)
246 def set_file_checksums(testcase
):
247 """ Set the fake file checksums for the test case. """
248 testcase
.fake_checksum_by_file
= {
249 os
.path
.basename(file_path
): make_unique_slug(testcase
)
250 for file_path
in testcase
.fake_upload_file_paths
}
253 def set_file_sizes(testcase
):
254 """ Set the fake file sizes for the test case. """
255 testcase
.fake_size_by_file
= {
256 os
.path
.basename(file_path
): testcase
.getUniqueInteger()
257 for file_path
in testcase
.fake_upload_file_paths
}
260 def set_file_doubles(testcase
):
261 """ Set the file doubles for the test case. """
262 for file_path
in testcase
.fake_upload_file_paths
:
263 file_double
= FileDouble(file_path
)
264 file_double
.set_os_stat_scenario('okay')
265 file_double
.stat_result
= file_double
.stat_result
._replace
(
266 st_size
=testcase
.fake_size_by_file
[
267 os
.path
.basename(file_path
)],
269 file_double
.register_for_testcase(testcase
)
272 def setup_upload_file_fixtures(testcase
):
273 """ Set fixtures for fake files to upload for the test case. """
274 set_fake_upload_file_paths(testcase
)
275 set_file_checksums(testcase
)
276 set_file_sizes(testcase
)
277 set_file_doubles(testcase
)
280 def make_changes_file_path(file_dir_path
=None):
281 """ Make a filesystem path for the changes file. """
282 if file_dir_path
is None:
283 file_dir_path
= tempfile
.mktemp()
284 file_name
= os
.path
.basename(
285 "{base}.changes".format(base
=tempfile
.mktemp()))
286 file_path
= os
.path
.join(file_dir_path
, file_name
)
291 def setup_changes_file_fixtures(testcase
):
292 """ Set up fixtures for changes file doubles. """
293 file_path
= make_changes_file_path()
295 scenarios
= make_fake_file_scenarios(file_path
)
296 testcase
.changes_file_scenarios
= scenarios
298 file_doubles
= get_file_doubles_from_fake_file_scenarios(
300 setup_file_double_behaviour(testcase
, file_doubles
)
303 def set_changes_file_scenario(testcase
, name
):
304 """ Set the changes file scenario for this test case. """
305 scenario
= dict(testcase
.changes_file_scenarios
)[name
]
306 testcase
.changes_file_scenario
= scenario
307 testcase
.changes_file_double
= scenario
['file_double']
308 testcase
.changes_file_double
.register_for_testcase(testcase
)
311 class parse_changes_TestCase(
312 testscenarios
.WithScenarios
,
314 """ Base for test cases for `parse_changes` function. """
316 scenarios
= NotImplemented
319 """ Set up test fixtures. """
320 super(parse_changes_TestCase
, self
).setUp()
321 patch_system_interfaces(self
)
323 self
.test_infile
= StringIO()
326 class parse_changes_SuccessTestCase(parse_changes_TestCase
):
327 """ Success test cases for `parse_changes` function. """
331 for (name
, scenario
) in make_changes_file_scenarios()
332 if not name
.startswith('error'))
334 def test_gives_expected_result_for_infile(self
):
335 """ Should give the expected result for specified input file. """
336 result
= dput
.dput
.parse_changes(self
.file_double
.fake_file
)
337 normalised_result_set
= set(
338 (key
.lower(), value
.strip())
339 for (key
, value
) in result
.items())
341 set(self
.expected_result
.items()), normalised_result_set
)
344 class parse_changes_ErrorTestCase(parse_changes_TestCase
):
345 """ Error test cases for `parse_changes` function. """
349 for (name
, scenario
) in make_changes_file_scenarios()
350 if name
.startswith('error'))
352 def test_raises_expected_exception_for_infile(self
):
353 """ Should raise the expected exception for specified input file. """
354 with testtools
.ExpectedException(self
.expected_error
):
355 dput
.dput
.parse_changes(self
.file_double
.fake_file
)
358 class check_upload_variant_TestCase(
359 testscenarios
.WithScenarios
,
361 """ Test cases for `check_upload_variant` function. """
366 'architecture': "foo bar baz",
368 'expected_result': True,
372 'spam': "Lorem ipsum dolor sit amet",
374 'expected_result': False,
378 'architecture': "source",
380 'expected_result': False,
382 ('source-and-others', {
384 'architecture': "foo source bar",
386 'expected_result': False,
391 """ Set up test fixtures. """
392 super(check_upload_variant_TestCase
, self
).setUp()
393 patch_system_interfaces(self
)
395 self
.set_changes_document(self
.fields
)
398 def set_changes_document(self
, fields
):
399 """ Set the package changes document based on specified fields. """
400 self
.test_changes_document
= make_changes_document(fields
)
402 def set_test_args(self
):
403 """ Set the arguments for the test call to the function. """
405 'changes': self
.test_changes_document
,
409 def test_returns_expected_result_for_changes_document(self
):
410 """ Should return expected result for specified changes document. """
411 result
= dput
.dput
.check_upload_variant(**self
.test_args
)
412 self
.assertEqual(self
.expected_result
, result
)
414 def test_emits_debug_message_showing_architecture(self
):
415 """ Should emit a debug message for the specified architecture. """
416 if 'architecture' not in self
.fields
:
417 self
.skipTest("Architecture field not in this scenario")
418 self
.test_args
['debug'] = True
419 dput
.dput
.check_upload_variant(**self
.test_args
)
420 expected_output
= textwrap
.dedent("""\
421 D: Architecture: {arch}
422 """).format(arch
=self
.fields
['architecture'])
423 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
425 def test_emits_debug_message_for_binary_upload(self
):
426 """ Should emit a debug message for the specified architecture. """
427 triggers_binaryonly
= bool(self
.expected_result
)
428 if not triggers_binaryonly
:
429 self
.skipTest("Scenario does not trigger binary-only upload")
430 self
.test_args
['debug'] = True
431 dput
.dput
.check_upload_variant(**self
.test_args
)
432 expected_output
= textwrap
.dedent("""\
433 D: Doing a binary upload only.
435 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
438 SourceCheckResult
= collections
.namedtuple(
439 'SourceCheckResult', ['include_orig', 'include_tar'])
442 class source_check_TestCase(
443 testscenarios
.WithScenarios
,
445 """ Test cases for `source_check` function. """
447 default_expected_result
= SourceCheckResult(
448 include_orig
=False, include_tar
=False)
452 'expected_result': default_expected_result
,
454 ('no-epoch native-version', {
455 'upstream_version': "1.2",
456 'expected_result': SourceCheckResult(
457 include_orig
=False, include_tar
=True),
459 ('epoch native-version', {
461 'upstream_version': "1.2",
462 'expected_result': SourceCheckResult(
463 include_orig
=False, include_tar
=True),
465 ('no-epoch debian-release', {
466 'upstream_version': "1.2",
468 'expected_result': SourceCheckResult(
469 include_orig
=False, include_tar
=True),
471 ('epoch debian-release', {
473 'upstream_version': "1.2",
475 'expected_result': SourceCheckResult(
476 include_orig
=False, include_tar
=True),
478 ('no-epoch new-upstream-version', {
479 'upstream_version': "1.2",
481 'expected_result': SourceCheckResult(
482 include_orig
=True, include_tar
=False),
484 ('epoch new_upstream-version', {
486 'upstream_version': "1.2",
488 'expected_result': SourceCheckResult(
489 include_orig
=True, include_tar
=False),
492 'upstream_version': "1.2",
494 'expected_result': SourceCheckResult(
495 include_orig
=False, include_tar
=True),
499 'upstream_version': "1.2",
501 'expected_result': SourceCheckResult(
502 include_orig
=False, include_tar
=True),
504 ('no-epoch nmu before-first-release', {
505 'upstream_version': "1.2",
507 'expected_result': SourceCheckResult(
508 include_orig
=True, include_tar
=False),
510 ('epoch nmu before-first-release', {
512 'upstream_version': "1.2",
514 'expected_result': SourceCheckResult(
515 include_orig
=True, include_tar
=False),
517 ('no-epoch nmu after-first-release', {
518 'upstream_version': "1.2",
520 'expected_result': SourceCheckResult(
521 include_orig
=True, include_tar
=False),
523 ('epoch nmu after-first-release', {
525 'upstream_version': "1.2",
527 'expected_result': SourceCheckResult(
528 include_orig
=True, include_tar
=False),
532 for (scenario_name
, scenario
) in scenarios
:
534 if 'upstream_version' in scenario
:
535 version_string
= scenario
['upstream_version']
536 if 'epoch' in scenario
:
537 version_string
= "{epoch}:{version}".format(
538 epoch
=scenario
['epoch'], version
=version_string
)
539 if 'release' in scenario
:
540 version_string
= "{version}-{release}".format(
541 version
=version_string
, release
=scenario
['release'])
542 fields
.update({'version': version_string
})
543 scenario
['version'] = version_string
544 scenario
['changes_document'] = make_changes_document(fields
)
545 del scenario_name
, scenario
546 del fields
, version_string
549 """ Set up test fixtures. """
550 super(source_check_TestCase
, self
).setUp()
551 patch_system_interfaces(self
)
554 'changes': self
.changes_document
,
558 def test_returns_expected_result_for_changes_document(self
):
559 """ Should return expected result for specified changes document. """
560 result
= dput
.dput
.source_check(**self
.test_args
)
561 self
.assertEqual(self
.expected_result
, result
)
563 def test_emits_version_string_debug_message_only_if_version(self
):
564 """ Should emit message for version only if has version. """
565 self
.test_args
['debug'] = True
566 version
= getattr(self
, 'version', None)
567 message_lead
= "D: Package Version:"
568 expected_output
= textwrap
.dedent("""\
571 lead
=message_lead
, version
=version
)
572 dput
.dput
.source_check(**self
.test_args
)
573 if hasattr(self
, 'version'):
574 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
576 self
.assertNotIn(message_lead
, sys
.stdout
.getvalue())
578 def test_emits_epoch_debug_message_only_if_epoch(self
):
579 """ Should emit message for epoch only if has an epoch. """
580 self
.test_args
['debug'] = True
581 dput
.dput
.source_check(**self
.test_args
)
582 expected_output
= textwrap
.dedent("""\
585 dput
.dput
.source_check(**self
.test_args
)
586 if (hasattr(self
, 'epoch') and hasattr(self
, 'release')):
587 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
589 self
.assertNotIn(expected_output
, sys
.stdout
.getvalue())
591 def test_emits_upstream_version_debug_message_only_if_nonnative(self
):
592 """ Should emit message for upstream version only if non-native. """
593 self
.test_args
['debug'] = True
594 upstream_version
= getattr(self
, 'upstream_version', None)
595 message_lead
= "D: Upstream Version:"
596 expected_output
= textwrap
.dedent("""\
599 lead
=message_lead
, version
=upstream_version
)
600 dput
.dput
.source_check(**self
.test_args
)
601 if hasattr(self
, 'release'):
602 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
604 self
.assertNotIn(message_lead
, sys
.stdout
.getvalue())
606 def test_emits_debian_release_debug_message_only_if_nonnative(self
):
607 """ Should emit message for Debian release only if non-native. """
608 self
.test_args
['debug'] = True
609 debian_release
= getattr(self
, 'release', None)
610 message_lead
= "D: Debian Version:"
611 expected_output
= textwrap
.dedent("""\
614 lead
=message_lead
, version
=debian_release
)
615 dput
.dput
.source_check(**self
.test_args
)
616 if hasattr(self
, 'release'):
617 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
619 self
.assertNotIn(message_lead
, sys
.stdout
.getvalue())
622 class verify_files_TestCase(
623 testscenarios
.WithScenarios
,
625 """ Test cases for `verify_files` function. """
631 unsigned_upload
=None,
638 'check_upload_variant_return_value': False,
640 ('include foo.tar.gz', {
641 'additional_file_suffixes': [".tar.gz"],
642 'source_check_result': SourceCheckResult(
643 include_orig
=False, include_tar
=True),
645 ('include foo.orig.tar.gz', {
646 'additional_file_suffixes': [".orig.tar.gz"],
647 'source_check_result': SourceCheckResult(
648 include_orig
=True, include_tar
=False),
650 ('unexpected foo.tar.gz', {
651 'additional_file_suffixes': [".tar.gz"],
652 'expected_rejection_message': (
653 "Package includes a .tar.gz file although"),
655 ('unexpected foo.orig.tar.gz', {
656 'additional_file_suffixes': [".orig.tar.gz"],
657 'expected_rejection_message': (
658 "Package includes an .orig.tar.gz file although"),
660 ('no distribution', {
661 'test_distribution': None,
666 """ Set up test fixtures. """
667 super(verify_files_TestCase
, self
).setUp()
668 patch_system_interfaces(self
)
670 self
.file_double_by_path
= {}
671 set_config(self
, 'exist-simple')
672 patch_runtime_config_options(self
)
676 setup_changes_file_fixtures(self
)
677 set_changes_file_scenario(self
, 'exist-minimal')
678 self
.test_args
.update(dict(
679 path
=os
.path
.dirname(self
.changes_file_double
.path
),
680 filename
=os
.path
.basename(self
.changes_file_double
.path
),
685 setup_upload_file_fixtures(self
)
686 self
.set_expected_files_to_upload()
688 self
.patch_checksum_test()
689 self
.patch_parse_changes()
690 self
.patch_check_upload_variant()
691 self
.set_expected_binary_upload()
692 self
.set_expected_source_control_file_path()
693 self
.patch_version_check()
694 self
.patch_verify_signature()
695 self
.patch_source_check()
697 def set_expected_files_to_upload(self
):
698 """ Set the expected `files_to_upload` result for this test case. """
699 self
.expected_files_to_upload
= set(
700 path
for path
in self
.fake_upload_file_paths
)
701 self
.expected_files_to_upload
.add(self
.changes_file_double
.path
)
703 def patch_checksum_test(self
):
704 """ Patch `checksum_test` function for this test case. """
705 func_patcher
= mock
.patch
.object(
706 dput
.dput
, "checksum_test", autospec
=True)
707 mock_func
= func_patcher
.start()
708 self
.addCleanup(func_patcher
.stop
)
710 def get_checksum_for_file(path
, hash_name
):
711 return self
.fake_checksum_by_file
[os
.path
.basename(path
)]
712 mock_func
.side_effect
= get_checksum_for_file
714 def set_changes_document(self
):
715 """ Set the changes document for this test case. """
716 self
.changes_document
= make_changes_document(
718 upload_params_by_name
=self
.upload_params_by_name
)
719 self
.test_distribution
= getattr(self
, 'test_distribution', "lorem")
720 if self
.test_distribution
is not None:
721 self
.changes_document
.add_header(
722 'distribution', self
.test_distribution
)
723 self
.runtime_config_parser
.set(
724 self
.test_args
['host'], 'allowed_distributions',
725 self
.test_distribution
)
727 dput
.dput
.parse_changes
.return_value
= self
.changes_document
729 def set_upload_params(self
):
730 """ Set the upload parameters for this test case. """
731 self
.upload_params_by_name
= make_upload_files_params(
732 self
.fake_checksum_by_file
,
733 self
.fake_size_by_file
)
735 def patch_parse_changes(self
):
736 """ Patch `parse_changes` function for this test case. """
737 func_patcher
= mock
.patch
.object(
738 dput
.dput
, "parse_changes", autospec
=True)
740 self
.addCleanup(func_patcher
.stop
)
742 self
.set_upload_params()
743 self
.set_changes_document()
745 def patch_check_upload_variant(self
):
746 """ Patch `check_upload_variant` function for this test case. """
747 if not hasattr(self
, 'check_upload_variant_return_value'):
748 self
.check_upload_variant_return_value
= True
750 func_patcher
= mock
.patch
.object(
751 dput
.dput
, "check_upload_variant", autospec
=True,
752 return_value
=self
.check_upload_variant_return_value
)
754 self
.addCleanup(func_patcher
.stop
)
756 def patch_version_check(self
):
757 """ Patch `version_check` function for this test case. """
758 func_patcher
= mock
.patch
.object(
759 dput
.dput
, "version_check", autospec
=True)
761 self
.addCleanup(func_patcher
.stop
)
763 def patch_verify_signature(self
):
764 """ Patch `verify_signature` function for this test case. """
765 func_patcher
= mock
.patch
.object(
766 dput
.dput
, "verify_signature", autospec
=True)
768 self
.addCleanup(func_patcher
.stop
)
770 def patch_source_check(self
):
771 """ Patch `source_check` function for this test case. """
772 func_patcher
= mock
.patch
.object(
773 dput
.dput
, "source_check", autospec
=True)
774 mock_func
= func_patcher
.start()
775 self
.addCleanup(func_patcher
.stop
)
777 source_check_result
= getattr(
778 self
, 'source_check_result', SourceCheckResult(
779 include_orig
=False, include_tar
=False))
780 mock_func
.return_value
= source_check_result
782 def set_test_args(self
):
783 """ Set test args for this test case. """
784 extra_args
= getattr(self
, 'extra_args', {})
785 self
.test_args
= self
.default_args
.copy()
786 self
.test_args
['config'] = self
.runtime_config_parser
787 self
.test_args
.update(extra_args
)
789 def set_expected_binary_upload(self
):
790 """ Set expected value for `binary_upload` flag. """
791 self
.expected_binary_upload
= self
.check_upload_variant_return_value
793 def set_expected_source_control_file_path(self
):
794 """ Set expected value for source control file path. """
796 os
.path
.basename(file_path
)
797 for file_path
in self
.fake_upload_file_paths
798 if file_path
.endswith(".dsc"))
799 if not self
.expected_binary_upload
:
800 self
.expected_source_control_file_path
= os
.path
.join(
801 os
.path
.dirname(self
.changes_file_double
.path
), file_name
)
803 self
.expected_source_control_file_path
= ""
805 def test_emits_changes_file_path_debug_message(self
):
806 """ Should emit debug message for changes file path. """
807 self
.test_args
['debug'] = True
808 dput
.dput
.verify_files(**self
.test_args
)
809 expected_output
= textwrap
.dedent("""\
810 D: Validating contents of changes file {path}
811 """).format(path
=self
.changes_file_double
.path
)
812 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
814 def test_calls_sys_exit_if_input_read_denied(self
):
815 """ Should call `sys.exit` if input file read access is denied. """
816 set_changes_file_scenario(self
, 'error-read-denied')
817 with testtools
.ExpectedException(FakeSystemExit
):
818 dput
.dput
.verify_files(**self
.test_args
)
819 expected_output
= textwrap
.dedent("""\
821 """).format(path
=self
.changes_file_double
.path
)
822 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
823 sys
.exit
.assert_called_with(EXIT_STATUS_FAILURE
)
825 def test_calls_parse_changes_with_changes_files(self
):
826 """ Should call `parse_changes` with changes file. """
827 dput
.dput
.verify_files(**self
.test_args
)
828 dput
.dput
.parse_changes
.assert_called_with(
829 self
.changes_file_double
.fake_file
)
831 def test_calls_check_upload_variant_with_changes_document(self
):
832 """ Should call `check_upload_variant` with changes document. """
833 dput
.dput
.verify_files(**self
.test_args
)
834 dput
.dput
.check_upload_variant
.assert_called_with(
835 self
.changes_document
, mock
.ANY
)
837 def test_emits_upload_dsc_file_debug_message(self
):
838 """ Should emit debug message for ‘*.dsc’ file. """
839 if getattr(self
, 'check_upload_variant_return_value', True):
840 self
.skipTest("Binary package upload for this scenario")
841 self
.test_args
['debug'] = True
842 dput
.dput
.verify_files(**self
.test_args
)
843 dsc_file_path
= next(
844 os
.path
.basename(file_path
)
845 for file_path
in self
.fake_upload_file_paths
846 if file_path
.endswith(".dsc"))
847 expected_output
= textwrap
.dedent("""\
849 """).format(path
=dsc_file_path
)
851 sys
.stdout
.getvalue(),
852 testtools
.matchers
.Contains(expected_output
))
854 def test_calls_sys_exit_when_source_upload_omits_dsc_file(self
):
855 """ Should call `sys.exit` when source upload omits ‘*.dsc’ file. """
856 if getattr(self
, 'check_upload_variant_return_value', True):
857 self
.skipTest("Binary package upload for this scenario")
858 self
.fake_checksum_by_file
= dict(
859 (file_path
, checksum
)
860 for (file_path
, checksum
)
861 in self
.fake_checksum_by_file
.items()
862 if not file_path
.endswith(".dsc"))
863 self
.set_upload_params()
864 self
.set_changes_document()
865 with testtools
.ExpectedException(FakeSystemExit
):
866 dput
.dput
.verify_files(**self
.test_args
)
867 expected_output
= textwrap
.dedent("""\
868 Error: no dsc file found in sourceful upload
871 sys
.stderr
.getvalue(),
872 testtools
.matchers
.Contains(expected_output
))
873 sys
.exit
.assert_called_with(EXIT_STATUS_FAILURE
)
875 def test_calls_version_check_when_specified_in_config(self
):
876 """ Should call `version_check` when specified in config. """
877 self
.runtime_config_parser
.set(
878 self
.test_args
['host'], 'check_version', "true")
879 dput
.dput
.verify_files(**self
.test_args
)
880 dput
.dput
.version_check
.assert_called_with(
881 os
.path
.dirname(self
.changes_file_double
.path
),
882 self
.changes_document
,
883 self
.test_args
['debug'])
885 def test_calls_version_check_when_specified_in_args(self
):
886 """ Should call `version_check` when specified in arguments. """
887 self
.test_args
['check_version'] = True
888 dput
.dput
.verify_files(**self
.test_args
)
889 dput
.dput
.version_check
.assert_called_with(
890 os
.path
.dirname(self
.changes_file_double
.path
),
891 self
.changes_document
,
892 self
.test_args
['debug'])
894 def test_calls_sys_exit_when_host_section_not_in_config(self
):
895 """ Should call `sys.exit` when specified host not in config. """
896 self
.runtime_config_parser
.remove_section(self
.test_args
['host'])
897 with testtools
.ExpectedException(FakeSystemExit
):
898 dput
.dput
.verify_files(**self
.test_args
)
899 expected_output
= textwrap
.dedent("""\
900 Error in config file:
904 sys
.stderr
.getvalue(),
905 testtools
.matchers
.DocTestMatches(
906 expected_output
, doctest
.ELLIPSIS
))
907 sys
.exit
.assert_called_with(EXIT_STATUS_FAILURE
)
909 def test_calls_verify_signature_with_expected_args(self
):
910 """ Should call `verify_signature` with expected args. """
911 dput
.dput
.verify_files(**self
.test_args
)
912 dput
.dput
.verify_signature
.assert_called_with(
913 self
.test_args
['host'],
914 self
.changes_file_double
.path
,
915 self
.expected_source_control_file_path
,
916 self
.runtime_config_parser
,
917 self
.test_args
['check_only'],
918 self
.test_args
['unsigned_upload'], mock
.ANY
,
919 self
.test_args
['debug'])
921 def test_calls_source_check_with_changes_document(self
):
922 """ Should call `source_check` with changes document. """
923 dput
.dput
.verify_files(**self
.test_args
)
924 dput
.dput
.source_check
.assert_called_with(
925 self
.changes_document
, self
.test_args
['debug'])
927 def test_emits_upload_file_path_debug_message(self
):
928 """ Should emit debug message for each upload file path. """
929 self
.test_args
['debug'] = True
930 dput
.dput
.verify_files(**self
.test_args
)
931 for file_path
in self
.fake_upload_file_paths
:
932 expected_output
= textwrap
.dedent("""\
933 D: File to upload: {path}
934 """).format(path
=file_path
)
936 sys
.stdout
.getvalue(),
937 testtools
.matchers
.Contains(expected_output
))
939 def test_calls_checksum_test_with_upload_files(self
):
940 """ Should call `checksum_test` with each upload file path. """
941 dput
.dput
.verify_files(**self
.test_args
)
943 mock
.call(file_path
, mock
.ANY
)
944 for file_path
in self
.fake_upload_file_paths
]
945 dput
.dput
.checksum_test
.assert_has_calls(
946 expected_calls
, any_order
=True)
948 def set_bogus_file_checksums(self
):
949 """ Set bogus file checksums that will not match. """
950 self
.fake_checksum_by_file
= {
951 file_name
: self
.getUniqueString()
952 for file_name
in self
.fake_checksum_by_file
}
954 def test_emits_checksum_okay_debug_message(self
):
955 """ Should emit debug message checksum okay for each file. """
956 self
.test_args
['debug'] = True
957 dput
.dput
.verify_files(**self
.test_args
)
958 for file_path
in self
.fake_upload_file_paths
:
959 expected_output
= textwrap
.dedent("""\
960 D: Checksum for {path} is fine
961 """).format(path
=file_path
)
963 sys
.stdout
.getvalue(),
964 testtools
.matchers
.Contains(expected_output
))
966 def test_emits_checksum_mismatch_debug_message(self
):
967 """ Should emit debug message when a checksum does not match. """
968 self
.test_args
['debug'] = True
969 self
.set_bogus_file_checksums()
970 with testtools
.ExpectedException(FakeSystemExit
):
971 dput
.dput
.verify_files(**self
.test_args
)
972 expected_output
= textwrap
.dedent("""\
974 D: Checksum from .changes: ...
975 D: Generated Checksum: ...
979 sys
.stdout
.getvalue(),
980 testtools
.matchers
.DocTestMatches(
981 expected_output
, doctest
.ELLIPSIS
))
983 def test_calls_sys_exit_when_checksum_mismatch(self
):
984 """ Should call `sys.exit` when a checksum does not match. """
985 specified_checksum_by_file
= self
.fake_checksum_by_file
986 self
.set_bogus_file_checksums()
987 with testtools
.ExpectedException(FakeSystemExit
):
988 dput
.dput
.verify_files(**self
.test_args
)
990 expected_output_for_files
= [
992 Checksum doesn't match for {file_name}
994 file_name
=os
.path
.join(
995 os
.path
.dirname(self
.changes_file_double
.path
),
997 specified_hash
=specified_hash
,
998 computed_hash
=self
.fake_checksum_by_file
[file_name
])
999 for (file_name
, specified_hash
)
1000 in specified_checksum_by_file
.items()]
1002 sys
.stdout
.getvalue(),
1003 testtools
.matchers
.MatchesAny(*[
1004 testtools
.matchers
.Contains(expected_output
)
1005 for expected_output
in expected_output_for_files
]))
1006 sys
.exit
.assert_called_with(EXIT_STATUS_FAILURE
)
1008 def test_calls_os_stat_with_upload_files(self
):
1009 """ Should call `os.stat` with each upload file path. """
1010 dput
.dput
.verify_files(**self
.test_args
)
1012 mock
.call(file_path
)
1013 for file_path
in self
.fake_upload_file_paths
]
1014 os
.stat
.assert_has_calls(expected_calls
, any_order
=True)
1016 def set_bogus_file_sizes(self
):
1017 """ Set bogus file sizes that will not match. """
1018 file_double_registry
= FileDouble
.get_registry_for_testcase(self
)
1019 for file_name
in self
.fake_size_by_file
:
1020 bogus_size
= self
.getUniqueInteger()
1021 self
.fake_size_by_file
[file_name
] = bogus_size
1022 file_path
= os
.path
.join(
1023 os
.path
.dirname(self
.changes_file_double
.path
),
1025 file_double
= file_double_registry
[file_path
]
1026 file_double
.stat_result
= file_double
.stat_result
._replace
(
1029 def test_emits_size_mismatch_debug_message(self
):
1030 """ Should emit debug message when a size does not match. """
1031 self
.test_args
['debug'] = True
1032 self
.set_bogus_file_sizes()
1033 dput
.dput
.verify_files(**self
.test_args
)
1034 expected_output
= textwrap
.dedent("""\
1036 D: size from .changes: ...
1037 D: calculated size: ...
1041 sys
.stdout
.getvalue(),
1042 testtools
.matchers
.DocTestMatches(
1043 expected_output
, doctest
.ELLIPSIS
))
1045 def test_emits_size_mismatch_message_for_each_file(self
):
1046 """ Should emit error message for each file with size mismatch. """
1047 self
.set_bogus_file_sizes()
1048 dput
.dput
.verify_files(**self
.test_args
)
1049 for file_path
in self
.fake_upload_file_paths
:
1050 expected_output
= textwrap
.dedent("""\
1051 size doesn't match for {path}
1052 """).format(path
=file_path
)
1054 sys
.stdout
.getvalue(),
1055 testtools
.matchers
.Contains(expected_output
))
1057 def test_emits_rejection_warning_when_unexpected_tarball(self
):
1058 """ Should emit warning of rejection when unexpected tarball. """
1059 if not hasattr(self
, 'expected_rejection_message'):
1060 self
.skipTest("No rejection message expected")
1061 dput
.dput
.verify_files(**self
.test_args
)
1062 sys
.stderr
.write("calls: {calls!r}\n".format(
1063 calls
=sys
.stdout
.write
.mock_calls
))
1065 sys
.stdout
.getvalue(),
1066 testtools
.matchers
.Contains(self
.expected_rejection_message
))
1068 def test_raises_error_when_distribution_mismatch(self
):
1069 """ Should raise error when distribution mismatch against allowed. """
1070 if not getattr(self
, 'test_distribution', None):
1071 self
.skipTest("No distribution set for this test case")
1072 self
.runtime_config_parser
.set(
1073 self
.test_args
['host'], 'allowed_distributions',
1075 with testtools
.ExpectedException(dputhelper
.DputUploadFatalException
):
1076 dput
.dput
.verify_files(**self
.test_args
)
1078 def test_emits_changes_file_upload_debug_message(self
):
1079 """ Should emit debug message for upload of changes file. """
1080 self
.test_args
['debug'] = True
1081 dput
.dput
.verify_files(**self
.test_args
)
1082 expected_output
= textwrap
.dedent("""\
1083 D: File to upload: {path}
1084 """).format(path
=self
.changes_file_double
.path
)
1085 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1087 def test_returns_expected_files_to_upload_collection(self
):
1088 """ Should return expected `files_to_upload` collection value. """
1089 result
= dput
.dput
.verify_files(**self
.test_args
)
1090 expected_result
= self
.expected_files_to_upload
1091 self
.assertEqual(expected_result
, set(result
))
1094 class guess_upload_host_TestCase(
1095 testscenarios
.WithScenarios
,
1096 testtools
.TestCase
):
1097 """ Test cases for `guess_upload_host` function. """
1099 changes_file_scenarios
= [
1100 ('no-distribution', {
1101 'fake_file': StringIO(textwrap
.dedent("""\
1103 Lorem ipsum dolor sit amet
1106 ('distribution-spam', {
1107 'fake_file': StringIO(textwrap
.dedent("""\
1110 Lorem ipsum dolor sit amet
1113 ('distribution-beans', {
1114 'fake_file': StringIO(textwrap
.dedent("""\
1117 Lorem ipsum dolor sit amet
1123 ('distribution-found-of-one', {
1124 'changes_file_scenario_name': "distribution-spam",
1125 'test_distribution': "spam",
1126 'config_scenario_name': "exist-distribution-one",
1127 'expected_host': "foo",
1129 ('distribution-notfound-of-one', {
1130 'changes_file_scenario_name': "distribution-beans",
1131 'test_distribution': "beans",
1132 'config_scenario_name': "exist-distribution-one",
1133 'expected_host': "ftp-master",
1135 ('distribution-first-of-three', {
1136 'changes_file_scenario_name': "distribution-spam",
1137 'test_distribution': "spam",
1138 'config_scenario_name': "exist-distribution-three",
1139 'expected_host': "foo",
1141 ('distribution-last-of-three', {
1142 'changes_file_scenario_name': "distribution-beans",
1143 'test_distribution': "beans",
1144 'config_scenario_name': "exist-distribution-three",
1145 'expected_host': "foo",
1147 ('no-configured-distribution', {
1148 'changes_file_scenario_name': "distribution-beans",
1149 'config_scenario_name': "exist-distribution-none",
1150 'expected_host': "ftp-master",
1152 ('no-distribution', {
1153 'changes_file_scenario_name': "no-distribution",
1154 'config_scenario_name': "exist-simple",
1155 'expected_host': "ftp-master",
1157 ('default-distribution', {
1158 'config_scenario_name': "exist-default-distribution-only",
1159 'config_default_default_host_main': "consecteur",
1160 'expected_host': "consecteur",
1165 """ Set up test fixtures. """
1166 super(guess_upload_host_TestCase
, self
).setUp()
1167 patch_system_interfaces(self
)
1171 getattr(self
, 'config_scenario_name', 'exist-minimal'))
1172 patch_runtime_config_options(self
)
1174 self
.setup_changes_file_fixtures()
1175 set_changes_file_scenario(
1177 getattr(self
, 'changes_file_scenario_name', 'no-distribution'))
1179 self
.set_test_args()
1181 def set_test_args(self
):
1182 """ Set the arguments for the test call to the function. """
1183 self
.test_args
= dict(
1184 path
=os
.path
.dirname(self
.changes_file_double
.path
),
1185 filename
=os
.path
.basename(self
.changes_file_double
.path
),
1186 config
=self
.runtime_config_parser
,
1189 def setup_changes_file_fixtures(self
):
1190 """ Set up fixtures for fake changes file. """
1191 file_path
= make_changes_file_path()
1193 scenarios
= [s
for (__
, s
) in self
.changes_file_scenarios
]
1194 for scenario
in scenarios
:
1195 scenario
['file_double'] = FileDouble(
1196 file_path
, scenario
['fake_file'])
1197 setup_file_double_behaviour(
1199 get_file_doubles_from_fake_file_scenarios(scenarios
))
1201 def test_calls_sys_exit_if_read_denied(self
):
1202 """ Should call `sys.exit` if read permission denied. """
1203 self
.changes_file_double
.set_os_access_scenario('denied')
1204 self
.changes_file_double
.set_open_scenario('read_denied')
1205 with testtools
.ExpectedException(FakeSystemExit
):
1206 dput
.dput
.guess_upload_host(**self
.test_args
)
1207 expected_output
= textwrap
.dedent("""\
1209 """).format(path
=self
.changes_file_double
.path
)
1210 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1211 sys
.exit
.assert_called_with(EXIT_STATUS_FAILURE
)
1213 def test_returns_expected_host(self
):
1214 """ Should return expected host value. """
1215 result
= dput
.dput
.guess_upload_host(**self
.test_args
)
1216 self
.assertEqual(self
.expected_host
, result
)
1218 @mock.patch
.object(dput
.dput
, 'debug', True)
1219 def test_emits_debug_message_for_host(self
):
1220 """ Should emit a debug message for the discovered host. """
1221 config_parser
= self
.runtime_config_parser
1223 config_parser
.has_section(self
.expected_host
)
1224 and config_parser
.get(self
.expected_host
, 'distributions')):
1225 self
.skipTest("No distributions specified")
1226 dput
.dput
.guess_upload_host(**self
.test_args
)
1227 expected_output
= textwrap
.dedent("""\
1228 D: guessing host {host} based on distribution {dist}
1230 host
=self
.expected_host
, dist
=self
.test_distribution
)
1231 self
.assertIn(expected_output
, sys
.stdout
.getvalue())
1234 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
1236 # This is free software: you may copy, modify, and/or distribute this work
1237 # under the terms of the GNU General Public License as published by the
1238 # Free Software Foundation; version 3 of that license or any later version.
1239 # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details.
1246 # vim: fileencoding=utf-8 filetype=python :