示例#1
0
class TestGitExtraction(TestCase):
    def setUp(self):
        super().setUp()

        self.command = GitExtractionCommand()

    @mock.patch('olympia.git.management.commands.git_extraction.lock')
    def test_handle_does_not_run_if_switch_is_not_active(self, lock_mock):
        create_switch(SWITCH_NAME, active=False)

        self.command.handle()

        lock_mock.assert_not_called()

    @mock.patch('olympia.git.management.commands.git_extraction.lock')
    def test_handle_tries_to_acquire_lock(self, lock_mock):
        create_switch(SWITCH_NAME, active=True)

        self.command.handle()

        lock_mock.assert_called()

    def test_handle_calls_extract_addon_for_each_addon_in_queue(self):
        create_switch(SWITCH_NAME, active=True)
        addon = addon_factory()
        e1 = GitExtractionEntry.objects.create(addon=addon)
        # Create a duplicate add-on.
        e2 = GitExtractionEntry.objects.create(addon=addon)
        # Create another add-on.
        e3 = GitExtractionEntry.objects.create(addon=addon_factory())
        self.command.extract_addon = mock.Mock()

        self.command.handle()

        self.command.extract_addon.assert_has_calls(
            [mock.call(e1), mock.call(e2),
             mock.call(e3)])
        assert self.command.extract_addon.call_count == 3

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_aborts_when_addon_is_already_being_extracted(
            self, chain_mock):
        addon = addon_factory()
        entry = GitExtractionEntry.objects.create(addon=addon,
                                                  in_progress=True)

        self.command.extract_addon(entry)

        chain_mock.assert_not_called()
        assert GitExtractionEntry.objects.filter(pk=entry.pk).exists()

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_with_mock(self, chain_mock):
        addon = addon_factory()
        entry = GitExtractionEntry.objects.create(addon=addon)

        self.command.extract_addon(entry)

        chain_mock.assert_called_with(
            extract_versions_to_git.si(addon_pk=addon.pk,
                                       version_pks=[addon.current_version.pk]),
            remove_git_extraction_entry.si(addon.pk),
        )

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_called_more_than_once(self, chain_mock):
        addon = addon_factory()
        entry1 = GitExtractionEntry.objects.create(addon=addon)
        entry2 = GitExtractionEntry.objects.create(addon=addon)

        self.command.extract_addon(entry1)
        self.command.extract_addon(entry2)

        chain_mock.assert_called_with(
            extract_versions_to_git.si(addon_pk=addon.pk,
                                       version_pks=[addon.current_version.pk]),
            remove_git_extraction_entry.si(addon.pk),
        )
        chain_mock.call_count == 1

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_with_multiple_versions(self, chain_mock):
        addon = addon_factory()
        version1 = addon.current_version
        version2 = version_factory(addon=addon)
        version_deleted = version_factory(addon=addon, deleted=True)
        entry = GitExtractionEntry.objects.create(addon=addon)

        self.command.extract_addon(entry)

        chain_mock.assert_called_with(
            extract_versions_to_git.si(
                addon_pk=addon.pk,
                version_pks=[version1.pk, version2.pk, version_deleted.pk],
            ),
            remove_git_extraction_entry.si(addon.pk),
        )

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_remove_extraction_entry_immediately_when_no_version(
            self, chain_mock):
        addon = addon_factory()
        addon.current_version.update(git_hash='some hash')
        entry = GitExtractionEntry.objects.create(addon=addon)

        self.command.extract_addon(entry)

        chain_mock.assert_not_called()
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()

    def test_extract_addon(self):
        addon = addon_factory(file_kw={'filename': 'webextension_no_id.xpi'})
        version = addon.current_version
        repo = AddonGitRepository(addon)
        entry = GitExtractionEntry.objects.create(addon=addon)

        assert not version.git_hash
        assert not repo.is_extracted

        self.command.extract_addon(entry)
        addon.refresh_from_db()
        version.refresh_from_db()

        assert repo.is_extracted
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()
        assert version.git_hash

    # Overriding this setting is needed to tell Celery to run the error handler
    # (because we run Celery in eager mode in the test env). That being said,
    # Celery still raises errors so... we have to catch the exception too.
    @override_settings(CELERY_TASK_EAGER_PROPAGATES=False)
    def test_extract_addon_with_broken_ref_error_during_extraction(self):
        addon = addon_factory(file_kw={'filename': 'webextension_no_id.xpi'})
        version = addon.current_version
        repo = AddonGitRepository(addon)
        # Force the creation of the git repository.
        repo.git_repository
        assert repo.is_extracted
        # Create a broken ref, see:
        # https://github.com/mozilla/addons-server/issues/13590
        Path(f'{repo.git_repository_path}/.git/refs/heads/listed').touch()
        entry = GitExtractionEntry.objects.create(addon=addon)

        with pytest.raises(BrokenRefError):
            self.command.extract_addon(entry)

        addon.refresh_from_db()
        version.refresh_from_db()

        assert not repo.is_extracted
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()
        assert not version.git_hash
        new_entry = GitExtractionEntry.objects.get(addon_id=addon.pk)
        assert new_entry and new_entry.in_progress is None
示例#2
0
class TestGitExtraction(TestCase):
    def setUp(self):
        super().setUp()

        self.command = GitExtractionCommand()
        self.addon = addon_factory(file_kw={
            'filename': 'webextension_no_id.xpi',
        })

    @mock.patch('olympia.git.management.commands.git_extraction.lock')
    def test_handle_does_not_run_if_switch_is_not_active(self, lock_mock):
        create_switch(SWITCH_NAME, active=False)

        self.command.handle()

        lock_mock.assert_not_called()

    @mock.patch('olympia.git.management.commands.git_extraction.lock')
    def test_handle_tries_to_acquire_lock(self, lock_mock):
        create_switch(SWITCH_NAME, active=True)

        self.command.handle()

        lock_mock.assert_called()

    def test_handle_calls_extract_addon_for_each_addon_in_queue(self):
        create_switch(SWITCH_NAME, active=True)
        e1 = GitExtractionEntry.objects.create(addon=self.addon)
        # Create a duplicate add-on.
        e2 = GitExtractionEntry.objects.create(addon=self.addon)
        # Create another add-on.
        e3 = GitExtractionEntry.objects.create(addon=addon_factory())
        self.command.extract_addon = mock.Mock()

        self.command.handle()

        self.command.extract_addon.assert_has_calls(
            [mock.call(e3), mock.call(e2),
             mock.call(e1)])
        assert self.command.extract_addon.call_count == 3

    def test_handle_limits_the_number_of_entries_to_process(self):
        create_switch(SWITCH_NAME, active=True)
        GitExtractionEntry.objects.create(addon=self.addon)
        # Create a duplicate add-on.
        GitExtractionEntry.objects.create(addon=self.addon)
        # Create another add-on.
        e3 = GitExtractionEntry.objects.create(addon=addon_factory())
        e4 = GitExtractionEntry.objects.create(addon=addon_factory())
        self.command.extract_addon = mock.Mock()

        self.command.handle(None, limit=2)

        self.command.extract_addon.assert_has_calls(
            [mock.call(e4), mock.call(e3)])
        assert self.command.extract_addon.call_count == 2

    def test_handle_entries_with_same_created_date(self):
        create_switch(SWITCH_NAME, active=True)
        created = datetime.datetime(2020, 7, 5)
        # First entry inserted for the add-on.
        GitExtractionEntry.objects.create(addon=self.addon, created=created)
        # Second entry inserted for the add-on.
        GitExtractionEntry.objects.create(addon=self.addon, created=created)
        # Third entry inserted for the add-on but this one has
        # `in_progress=False` to simulate a previous execution of the task.
        # Without the right `order` value, other entries might be processed
        # instead of this one.
        e1_3 = GitExtractionEntry.objects.create(addon=self.addon,
                                                 created=created,
                                                 in_progress=False)
        self.command.extract_addon = mock.Mock()

        self.command.handle(None, limit=2)

        self.command.extract_addon.assert_has_calls(
            [mock.call(e1_3), mock.call(mock.ANY)])
        assert self.command.extract_addon.call_count == 2

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_aborts_when_addon_is_already_being_extracted(
            self, chain_mock):
        entry = GitExtractionEntry.objects.create(addon=self.addon,
                                                  in_progress=True)

        self.command.extract_addon(entry)

        chain_mock.assert_not_called()
        assert GitExtractionEntry.objects.filter(pk=entry.pk).exists()

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_with_mock(self, chain_mock):
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        self.command.extract_addon(entry)

        chain_mock.assert_called_with(
            extract_versions_to_git.si(
                addon_pk=self.addon.pk,
                version_pks=[self.addon.current_version.pk],
            ),
            remove_git_extraction_entry.si(self.addon.pk),
        )

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_called_more_than_once(self, chain_mock):
        entry1 = GitExtractionEntry.objects.create(addon=self.addon)
        entry2 = GitExtractionEntry.objects.create(addon=self.addon)

        self.command.extract_addon(entry1)
        self.command.extract_addon(entry2)

        chain_mock.assert_called_with(
            extract_versions_to_git.si(
                addon_pk=self.addon.pk,
                version_pks=[self.addon.current_version.pk],
            ),
            remove_git_extraction_entry.si(self.addon.pk),
        )
        chain_mock.call_count == 1

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_with_multiple_versions(self, chain_mock):
        version1 = self.addon.current_version
        version2 = version_factory(addon=self.addon, )
        version_deleted = version_factory(
            addon=self.addon,
            deleted=True,
        )
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        self.command.extract_addon(entry)

        chain_mock.assert_called_with(
            extract_versions_to_git.si(
                addon_pk=self.addon.pk,
                version_pks=[version1.pk, version2.pk, version_deleted.pk],
            ),
            remove_git_extraction_entry.si(self.addon.pk),
        )

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_continues_git_extraction(self, chain_mock):
        version1 = self.addon.current_version
        version2 = version_factory(addon=self.addon, )
        version_factory(addon=self.addon, )
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        self.command.extract_addon(entry, batch_size=2)

        chain_mock.assert_called_with(
            extract_versions_to_git.si(addon_pk=self.addon.pk,
                                       version_pks=[version1.pk, version2.pk]),
            continue_git_extraction.si(self.addon.pk),
        )

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_remove_entry_immediately_when_no_version(
            self, chain_mock):
        self.addon.current_version.update(git_hash='some hash')
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        self.command.extract_addon(entry)

        chain_mock.assert_not_called()
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()

    @mock.patch('olympia.git.management.commands.git_extraction.chain')
    def test_extract_addon_remove_entry_immediately_when_not_extension(
            self, chain_mock):
        self.addon.update(type=amo.ADDON_STATICTHEME)
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        self.command.extract_addon(entry)

        chain_mock.assert_not_called()
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()

    def test_extract_addon(self):
        version = self.addon.current_version
        repo = AddonGitRepository(self.addon)
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        assert not version.git_hash
        assert not repo.is_extracted

        self.command.extract_addon(entry)
        version.refresh_from_db()

        assert repo.is_extracted
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()
        assert version.git_hash

    def test_extract_addon_with_more_versions_than_batch_size(self):
        version_1 = self.addon.current_version
        version_2 = version_factory(
            addon=self.addon,
            file_kw={
                'filename': 'webextension_no_id.xpi',
            },
        )
        repo = AddonGitRepository(self.addon)
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        assert not version_1.git_hash
        assert not version_2.git_hash
        assert not repo.is_extracted

        # First execution of the CRON task.
        self.command.extract_addon(entry, batch_size=1)
        version_1.refresh_from_db()
        version_2.refresh_from_db()
        entry.refresh_from_db()

        assert repo.is_extracted
        assert version_1.git_hash
        # We only git-extracted the first version because of batch_size=1.
        assert not version_2.git_hash
        # We keep the entry and we set `in_progress` to `False` because we
        # still need to extract the second version.
        assert not entry.in_progress
        assert GitExtractionEntry.objects.filter(pk=entry.pk).exists()

        # Second execution of the CRON task.
        self.command.extract_addon(entry, batch_size=1)
        version_2.refresh_from_db()

        assert repo.is_extracted
        assert version_2.git_hash
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()

    # Overriding this setting is needed to tell Celery to run the error handler
    # (because we run Celery in eager mode in the test env). That being said,
    # Celery still raises errors so... we have to catch the exception too.
    @override_settings(CELERY_TASK_EAGER_PROPAGATES=False)
    def test_extract_addon_with_broken_ref_error_during_extraction(self):
        version = self.addon.current_version
        repo = AddonGitRepository(self.addon)
        # Force the creation of the git repository.
        repo.git_repository
        assert repo.is_extracted
        # Set the "creation time" of the git repository to something older than
        # 1 hour.
        update_git_repo_creation_time(repo, time=datetime.datetime(2020, 1, 1))
        # Create a broken ref, see:
        # https://github.com/mozilla/addons-server/issues/13590
        Path(f'{repo.git_repository_path}/.git/refs/heads/listed').touch()
        entry = GitExtractionEntry.objects.create(addon=self.addon)

        with pytest.raises(BrokenRefError):
            self.command.extract_addon(entry)

        version.refresh_from_db()

        assert not repo.is_extracted
        assert not GitExtractionEntry.objects.filter(pk=entry.pk).exists()
        assert not version.git_hash
        new_entry = GitExtractionEntry.objects.get(addon_id=self.addon.pk)
        assert new_entry and new_entry.in_progress is None