示例#1
0
 def test_afterCompletion_COMMITTED(self):
     txn = FakeTransaction()
     txn.status = TransactionStatus.COMMITTED
     fake_session = FakeRabbitSession()
     sync = RabbitSessionTransactionSync(fake_session)
     sync.afterCompletion(txn)
     self.assertEqual(["finish"], fake_session.log)
示例#2
0
 def test_afterCompletion_ACTIVE(self):
     txn = FakeTransaction()
     txn.status = TransactionStatus.ACTIVE
     fake_session = FakeRabbitSession()
     sync = RabbitSessionTransactionSync(fake_session)
     sync.afterCompletion(txn)
     self.assertEqual(["reset"], fake_session.log)
示例#3
0
 def test_afterCompletion_COMMITTED(self):
     txn = FakeTransaction()
     txn.status = TransactionStatus.COMMITTED
     fake_session = FakeRabbitSession()
     sync = RabbitSessionTransactionSync(fake_session)
     sync.afterCompletion(txn)
     self.assertEqual(["finish"], fake_session.log)
示例#4
0
 def test_afterCompletion_ACTIVE(self):
     txn = FakeTransaction()
     txn.status = TransactionStatus.ACTIVE
     fake_session = FakeRabbitSession()
     sync = RabbitSessionTransactionSync(fake_session)
     sync.afterCompletion(txn)
     self.assertEqual(["reset"], fake_session.log)
 def test_publishArchive_honours_disable_options(self):
     # The various --disable-* options disable the corresponding
     # publisher steps.
     possible_options = {
         "--disable-publishing": ["A_publish"],
         "--disable-domination": [
             "A2_markPocketsWithDeletionsDirty",
             "B_dominate",
         ],
         "--disable-apt": ["C_doFTPArchive", "createSeriesAliases"],
         "--disable-release": ["D_writeReleaseFiles"],
     }
     for option in possible_options:
         distro = self.makeDistro()
         script = self.makeScript(distro, args=[option])
         script.txn = FakeTransaction()
         publisher = FakePublisher()
         script.publishArchive(FakeArchive(), publisher)
         for check_option, steps in possible_options.items():
             for step in steps:
                 publisher_step = getattr(publisher, step)
                 if check_option == option:
                     self.assertEqual(0, publisher_step.call_count)
                 else:
                     self.assertEqual(1, publisher_step.call_count)
示例#6
0
    def test_dominate_imported_source_packages_dominates_deletions(self):
        # dominate_imported_source_packages dominates the source
        # packages that have been deleted from the Sources lists that
        # Gina imports.
        series = self.factory.makeDistroSeries()
        pocket = PackagePublishingPocket.RELEASE
        package = self.factory.makeSourcePackageName()
        pubs = [
            self.factory.makeSourcePackagePublishingHistory(
                archive=series.main_archive, distroseries=series,
                pocket=pocket, status=PackagePublishingStatus.PUBLISHED,
                sourcepackagerelease=self.factory.makeSourcePackageRelease(
                    sourcepackagename=package, version=version))
            for version in ['1.0', '1.1', '1.1a']]

        # In this scenario, 1.0 is a superseded release.
        pubs[0].supersede()
        logger = DevNullLogger()
        txn = FakeTransaction()
        dominate_imported_source_packages(
            txn, logger, series.distribution.name, series.name, pocket,
            FakePackagesMap({}))

        # The older, superseded release stays superseded; but the
        # releases that dropped out of the imported Sources list without
        # known successors are marked deleted.
        self.assertPublishingStates(
            pubs, [PackagePublishingStatus.SUPERSEDED,
            PackagePublishingStatus.DELETED, PackagePublishingStatus.DELETED])
示例#7
0
    def _getKeyGenerator(self, archive_reference=None, txn=None):
        """Return a `PPAKeyGenerator` instance.

        Monkey-patch the script object with a fake transaction manager
        and also make it use an alternative (fake and lighter) procedure
        to generate keys for each PPA.
        """
        test_args = []

        if archive_reference is not None:
            test_args.extend(['-A', archive_reference])

        key_generator = PPAKeyGenerator(
            name='ppa-generate-keys', test_args=test_args)

        if txn is None:
            txn = FakeTransaction()
        key_generator.txn = txn

        def fake_key_generation(archive):
            a_key = getUtility(IGPGKeySet).getByFingerprint(
                'ABCDEF0123456789ABCDDCBA0000111112345678')
            archive.signing_key_fingerprint = a_key.fingerprint
            archive.signing_key_owner = a_key.owner
            del get_property_cache(archive).signing_key

        key_generator.generateKey = fake_key_generation

        return key_generator
class TestCopying(TestCase):
    layer = LaunchpadZopelessLayer
    txn = FakeTransaction()

    def test_flagsHandling(self):
        """Flags are correctly restored, no matter what their values."""
        sid = getUtility(IDistributionSet)['debian']['sid']

        sid.hide_all_translations = True
        sid.defer_translation_imports = True
        copy_distroseries_translations(sid, self.txn, logging)
        self.assertTrue(sid.hide_all_translations)
        self.assertTrue(sid.defer_translation_imports)

        sid.hide_all_translations = True
        sid.defer_translation_imports = False
        copy_distroseries_translations(sid, self.txn, logging)
        self.assertTrue(sid.hide_all_translations)
        self.assertFalse(sid.defer_translation_imports)

        sid.hide_all_translations = False
        sid.defer_translation_imports = True
        copy_distroseries_translations(sid, self.txn, logging)
        self.assertFalse(sid.hide_all_translations)
        self.assertTrue(sid.defer_translation_imports)

        sid.hide_all_translations = False
        sid.defer_translation_imports = False
        copy_distroseries_translations(sid, self.txn, logging)
        self.assertFalse(sid.hide_all_translations)
        self.assertFalse(sid.defer_translation_imports)
    def _getKeyGenerator(self, ppa_owner_name=None, txn=None):
        """Return a `PPAKeyGenerator` instance.

        Monkey-patch the script object with a fake transaction manager
        and also make it use an alternative (fake and lighter) procedure
        to generate keys for each PPA.
        """
        test_args = []

        if ppa_owner_name is not None:
            test_args.extend(['-p', ppa_owner_name])

        key_generator = PPAKeyGenerator(name='ppa-generate-keys',
                                        test_args=test_args)

        if txn is None:
            txn = FakeTransaction()
        key_generator.txn = txn

        def fake_key_generation(archive):
            a_key = getUtility(IGPGKeySet).get(1)
            archive.signing_key = a_key

        key_generator.generateKey = fake_key_generation

        return key_generator
示例#10
0
    def test_dominate_imported_source_packages_dominates_imports(self):
        # dominate_imported_source_packages dominates the source
        # packages that Gina imports.
        logger = DevNullLogger()
        txn = FakeTransaction()
        series = self.factory.makeDistroSeries()
        pocket = PackagePublishingPocket.RELEASE
        package = self.factory.makeSourcePackageName()

        # Realistic situation: there's an older, superseded publication;
        # a series of active ones; and a newer, pending publication
        # that's not in the Sources lists yet.
        # Gina dominates the Published ones and leaves the rest alone.
        old_spph = self.factory.makeSourcePackagePublishingHistory(
            distroseries=series,
            archive=series.main_archive,
            pocket=pocket,
            status=PackagePublishingStatus.SUPERSEDED,
            sourcepackagerelease=self.factory.makeSourcePackageRelease(
                sourcepackagename=package, version='1.0'))

        active_spphs = [
            self.factory.makeSourcePackagePublishingHistory(
                distroseries=series,
                archive=series.main_archive,
                pocket=pocket,
                status=PackagePublishingStatus.PUBLISHED,
                sourcepackagerelease=self.factory.makeSourcePackageRelease(
                    sourcepackagename=package, version=version))
            for version in ['1.1', '1.1.1', '1.1.1.1']
        ]

        new_spph = self.factory.makeSourcePackagePublishingHistory(
            distroseries=series,
            archive=series.main_archive,
            pocket=pocket,
            status=PackagePublishingStatus.PENDING,
            sourcepackagerelease=self.factory.makeSourcePackageRelease(
                sourcepackagename=package, version='1.2'))

        spphs = [old_spph] + active_spphs + [new_spph]

        # Of the active publications, in this scenario, only one version
        # matches what Gina finds in the Sources list.  It stays
        # published; older active publications are superseded, newer
        # ones deleted.
        dominate_imported_source_packages(
            txn, logger, series.distribution.name, series.name, pocket,
            FakePackagesMap({package.name: [{
                'Version': '1.1.1'
            }]}))
        self.assertEqual([
            PackagePublishingStatus.SUPERSEDED,
            PackagePublishingStatus.SUPERSEDED,
            PackagePublishingStatus.PUBLISHED,
            PackagePublishingStatus.DELETED,
            PackagePublishingStatus.PENDING,
        ], [pub.status for pub in spphs])
 def test_publishArchive_uses_apt_ftparchive_for_main_archive(self):
     # For some types of archive, publishArchive invokes the
     # publisher's C_doFTPArchive method as a way of generating
     # indexes.
     distro = self.makeDistro()
     script = self.makeScript(distro)
     script.txn = FakeTransaction()
     publisher = FakePublisher()
     script.publishArchive(FakeArchive(), publisher)
     self.assertEqual(1, publisher.C_doFTPArchive.call_count)
     self.assertEqual(0, publisher.C_writeIndexes.call_count)
 def test_publishArchive_writes_own_indexes_for_ppa(self):
     # For some types of archive, publishArchive invokes the
     # publisher's C_writeIndexes as an alternative to
     # C_doFTPArchive.
     distro = self.makeDistro()
     script = self.makeScript(distro)
     script.txn = FakeTransaction()
     publisher = FakePublisher()
     script.publishArchive(FakeArchive(ArchivePurpose.PPA), publisher)
     self.assertEqual(0, publisher.C_doFTPArchive.call_count)
     self.assertEqual(1, publisher.C_writeIndexes.call_count)
 def makeScript(self, distribution=None, run_setup=True):
     """Create a script for testing."""
     if distribution is None:
         distribution = self.makeDistro()
     script = GenerateContentsFiles(test_args=['-d', distribution.name])
     script.logger = DevNullLogger()
     script.txn = FakeTransaction()
     if run_setup:
         script.setUp()
     else:
         script.distribution = distribution
     return script
    def test_does_not_overwrite_existing_pofile(self):
        # Sometimes a POFile we're about to copy to a new distroseries
        # has already been created there due to message sharing.  In
        # that case, the copying code leaves the existing POFile in
        # place and does not copy it.  (Nor does it raise an error.)
        existing_series = self.factory.makeDistroSeries(name='existing')
        new_series = self.factory.makeDistroSeries(
            name='new',
            distribution=existing_series.distribution,
            previous_series=existing_series)
        template = self.factory.makePOTemplate(distroseries=existing_series)
        pofile = self.factory.makePOFile(potemplate=template)
        self.factory.makeCurrentTranslationMessage(
            language=pofile.language,
            potmsgset=self.factory.makePOTMsgSet(potemplate=template))

        # Sabotage the pouring code so that when it's about to hit the
        # POFile table, it returns to us and we can simulate a race
        # condition.
        pour_table = MultiTableCopy._pourTable

        def pour_or_stop_at_pofile(self, holding_table, table, *args,
                                   **kwargs):
            args = (self, holding_table, table) + args
            if table.lower() == "pofile":
                raise EarlyExit(*args, **kwargs)
            else:
                return pour_table(*args, **kwargs)

        MultiTableCopy._pourTable = pour_or_stop_at_pofile
        try:
            copy_active_translations(new_series, FakeTransaction(),
                                     DevNullLogger())
        except EarlyExit as e:
            pour_args = e.args
            pour_kwargs = e.kwargs
        finally:
            MultiTableCopy._pourTable = pour_table

        # Simulate another POFile being created for new_series while the
        # copier was working.
        new_template = new_series.getTranslationTemplateByName(template.name)
        new_pofile = self.factory.makePOFile(potemplate=new_template,
                                             language=pofile.language)

        # Now continue pouring the POFile table.
        pour_table(*pour_args, **pour_kwargs)

        # The POFile we just created in our race condition stays in
        # place.  There is no error.
        resulting_pofile = new_template.getPOFileByLang(pofile.language.code)
        self.assertEqual(new_pofile, resulting_pofile)
 def test_publishArchive_drives_publisher(self):
     # publishArchive puts a publisher through its paces.  This work
     # ought to be in the publisher itself, so if you find this way
     # of doing things annoys you, that's your cue to help clean up!
     distro = self.makeDistro()
     script = self.makeScript(distro)
     script.txn = FakeTransaction()
     publisher = FakePublisher()
     script.publishArchive(FakeArchive(), publisher)
     self.assertEqual(1, publisher.A_publish.call_count)
     self.assertEqual(1,
                      publisher.A2_markPocketsWithDeletionsDirty.call_count)
     self.assertEqual(1, publisher.B_dominate.call_count)
     self.assertEqual(1, publisher.D_writeReleaseFiles.call_count)
 def makeScript(self, distribution, run_setup=True, extra_args=None):
     """Create a script for testing."""
     test_args = []
     if distribution is not None:
         test_args.extend(["-d", distribution.name])
     if extra_args is not None:
         test_args.extend(extra_args)
     script = GenerateExtraOverrides(test_args=test_args)
     script.logger = DevNullLogger()
     script.txn = FakeTransaction()
     if distribution is not None and run_setup:
         script.setUp()
     else:
         script.distribution = distribution
     return script
    def testGenerateKeyForASinglePPA(self):
        """Signing key generation for a single PPA.

        The 'signing_key' for the specified PPA is generated and
        the transaction is committed once.
        """
        cprov = getUtility(IPersonSet).getByName('cprov')
        self._fixArchiveForKeyGeneration(cprov.archive)

        self.assertTrue(cprov.archive.signing_key is None)

        txn = FakeTransaction()
        key_generator = self._getKeyGenerator(ppa_owner_name='cprov', txn=txn)
        key_generator.main()

        self.assertTrue(cprov.archive.signing_key is not None)
        self.assertEquals(txn.commit_count, 1)
示例#18
0
    def testGenerateKeyForAllPPA(self):
        """Signing key generation for all PPAs.

        The 'signing_key' for all 'pending-signing-key' PPAs are generated
        and the transaction is committed once for each PPA.
        """
        archives = list(getUtility(IArchiveSet).getPPAsPendingSigningKey())

        for archive in archives:
            self._fixArchiveForKeyGeneration(archive)
            self.assertTrue(archive.signing_key is None)

        txn = FakeTransaction()
        key_generator = self._getKeyGenerator(txn=txn)
        key_generator.main()

        for archive in archives:
            self.assertTrue(archive.signing_key is not None)

        self.assertEqual(txn.commit_count, len(archives))
 def test_publishes_only_selected_archives(self):
     # The script publishes only the archives returned by
     # getTargetArchives, for the distributions returned by
     # findDistros.
     distro = self.makeDistro()
     # The script gets a distribution and archive of its own, to
     # prove that any distros and archives other than what
     # findDistros and getTargetArchives return are ignored.
     script = self.makeScript()
     script.txn = FakeTransaction()
     script.findDistros = FakeMethod([distro])
     archive = FakeArchive()
     script.getTargetArchives = FakeMethod([archive])
     publisher = FakePublisher()
     script.getPublisher = FakeMethod(publisher)
     script.publishArchive = FakeMethod()
     script.main()
     [(args, kwargs)] = script.getPublisher.calls
     distro_arg, archive_arg = args[:2]
     self.assertEqual(distro, distro_arg)
     self.assertEqual(archive, archive_arg)
     self.assertEqual([((archive, publisher), {})],
                      script.publishArchive.calls)
示例#20
0
    def test_dominate_imported_sources_dominates_supported_series(self):
        series = self.factory.makeDistroSeries()
        pocket = PackagePublishingPocket.RELEASE
        package = self.factory.makeSourcePackageName()
        pubs = [
            self.factory.makeSourcePackagePublishingHistory(
                archive=series.main_archive, distroseries=series,
                pocket=pocket, status=PackagePublishingStatus.PUBLISHED,
                sourcepackagerelease=self.factory.makeSourcePackageRelease(
                    sourcepackagename=package, version=version))
            for version in ['1.0', '1.1', '1.1a']]

        # In this scenario, 1.0 is a superseded release.
        pubs[0].supersede()
        # Now set the series to SUPPORTED.
        series.status = SeriesStatus.SUPPORTED
        logger = DevNullLogger()
        txn = FakeTransaction()
        dominate_imported_source_packages(
            txn, logger, series.distribution.name, series.name, pocket,
            FakePackagesMap({}))
        self.assertPublishingStates(
            pubs, [PackagePublishingStatus.SUPERSEDED,
            PackagePublishingStatus.DELETED, PackagePublishingStatus.DELETED])
示例#21
0
    def runScript(
        self, archive_name=None, suite='hoary', user='******',
        exists_before=None, exists_after=None, exception_type=None,
        exception_text=None, extra_args=None, copy_archive_name=None,
        reason=None, output_substr=None, nonvirtualized=False):
        """Run the script to test.

        :type archive_name: `str`
        :param archive_name: the name of the copy archive to create.
        :type suite: `str`
        :param suite: the name of the copy archive suite.
        :type user: `str`
        :param user: the name of the user creating the archive.
        :type exists_before: `bool`
        :param exists_before: copy archive with given name should
            already exist if True.
        :type exists_after: `True`
        :param exists_after: the copy archive is expected to exist
            after script invocation if True.
        :type exception_type: type
        :param exception_type: the type of exception expected in case
            of failure.
        :type exception_text: `str`
        :param exception_text: expected exception text prefix in case
            of failure.
        :type extra_args: list of strings
        :param extra_args: additional arguments to be passed to the
            script (if any).
        :type copy_archive_name: `IArchive`
        :param copy_archive_name: optional copy archive instance, used for
            merge copy testing.
        :param reason: if empty do not provide '--reason' cmd line arg to
            the script
        :param output_substr: this must be part of the script's output
        """

        if copy_archive_name is None:
            now = int(time.time())
            if archive_name is None:
                archive_name = "ra%s" % now
        else:
            archive_name = copy_archive_name

        distro_name = 'ubuntu'
        distro = getUtility(IDistributionSet).getByName(distro_name)

        copy_archive = getUtility(IArchiveSet).getByDistroPurpose(
            distro, ArchivePurpose.COPY, archive_name)

        # Enforce these assertions only if the 'exists_before' flag was
        # specified in first place.
        if exists_before is not None:
            if exists_before:
                self.assertTrue(copy_archive is not None)
            else:
                self.assertTrue(copy_archive is None)

        # Command line arguments required for the invocation of the
        # 'populate-archive.py' script.
        script_args = [
            '--from-distribution', distro_name, '--from-suite', suite,
            '--to-distribution', distro_name, '--to-suite', suite,
            '--to-archive', archive_name, '--to-user', user,
            ]

        # Empty reason string indicates that the '--reason' command line
        # argument should be ommitted.
        if reason is not None and not reason.isspace():
            script_args.extend(['--reason', reason])
        elif reason is None:
            reason = "copy archive, %s" % datetime.ctime(datetime.utcnow())
            script_args.extend(['--reason', reason])

        if nonvirtualized:
            script_args.append('--nonvirtualized')

        if extra_args is not None:
            script_args.extend(extra_args)

        script = ArchivePopulator(
            'populate-archive', dbuser=config.uploader.dbuser,
            test_args=script_args)

        script.logger = BufferLogger()
        script.txn = FakeTransaction()

        if exception_type is not None:
            self.assertRaisesWithContent(
                exception_type, exception_text, script.mainTask)
        else:
            script.mainTask()

        # Does the script's output contain the specified sub-string?
        if output_substr is not None and not output_substr.isspace():
            output = script.logger.getLogBuffer()
            self.assertTrue(output_substr in output)

        copy_archive = getUtility(IArchiveSet).getByDistroPurpose(
            distro, ArchivePurpose.COPY, archive_name)

        # Enforce these assertions only if the 'exists_after' flag was
        # specified in first place.
        if exists_after is not None:
            if exists_after:
                self.assertTrue(copy_archive is not None)
            else:
                self.assertTrue(copy_archive is None)

        return copy_archive
class TestCopying(TestCaseWithFactory):
    layer = LaunchpadZopelessLayer
    txn = FakeTransaction()

    def test_flagsHandling(self):
        """Flags are correctly restored, no matter what their values."""
        sid = getUtility(IDistributionSet)['debian']['sid']
        source = sid.previous_series

        sid.hide_all_translations = True
        sid.defer_translation_imports = True
        copy_distroseries_translations(source, sid, self.txn, logging)
        self.assertTrue(sid.hide_all_translations)
        self.assertTrue(sid.defer_translation_imports)

        sid.hide_all_translations = True
        sid.defer_translation_imports = False
        copy_distroseries_translations(source, sid, self.txn, logging)
        self.assertTrue(sid.hide_all_translations)
        self.assertFalse(sid.defer_translation_imports)

        sid.hide_all_translations = False
        sid.defer_translation_imports = True
        copy_distroseries_translations(source, sid, self.txn, logging)
        self.assertFalse(sid.hide_all_translations)
        self.assertTrue(sid.defer_translation_imports)

        sid.hide_all_translations = False
        sid.defer_translation_imports = False
        copy_distroseries_translations(source, sid, self.txn, logging)
        self.assertFalse(sid.hide_all_translations)
        self.assertFalse(sid.defer_translation_imports)

    def test_published_packages_only(self):
        # copy_distroseries_translations's published_sources_only flag
        # restricts the copied templates to those with a corresponding
        # published source package in the target.
        distro = self.factory.makeDistribution(name='notbuntu')
        dapper = self.factory.makeDistroSeries(distribution=distro,
                                               name='dapper')
        spns = [self.factory.makeSourcePackageName() for i in range(3)]
        for spn in spns:
            self.factory.makePOTemplate(distroseries=dapper,
                                        sourcepackagename=spn)

        def get_template_spns(series):
            return [
                pot.sourcepackagename
                for pot in getUtility(IPOTemplateSet).getSubset(
                    distroseries=series)
            ]

        # Create a fresh series with two sources published.
        edgy = self.factory.makeDistroSeries(distribution=distro, name='edgy')
        self.factory.makeSourcePackagePublishingHistory(
            archive=edgy.main_archive,
            distroseries=edgy,
            sourcepackagename=spns[0],
            status=PackagePublishingStatus.PUBLISHED)
        self.factory.makeSourcePackagePublishingHistory(
            archive=edgy.main_archive,
            distroseries=edgy,
            sourcepackagename=spns[2],
            status=PackagePublishingStatus.PENDING)

        self.assertContentEqual(spns, get_template_spns(dapper))
        self.assertContentEqual([], get_template_spns(edgy))
        copy_distroseries_translations(dapper,
                                       edgy,
                                       self.txn,
                                       logging,
                                       published_sources_only=True)
        self.assertContentEqual([spns[0], spns[2]], get_template_spns(edgy))

    def test_published_packages_only_different_archive(self):
        # If an archive parameter is passed,
        # copy_distroseries_translations's published_sources_only flag
        # checks source package publications in that archive rather than in
        # the target's main archive.
        distro = self.factory.makeDistribution(name='notbuntu')
        dapper = self.factory.makeDistroSeries(distribution=distro,
                                               name='dapper')
        spns = [self.factory.makeSourcePackageName() for i in range(3)]
        for spn in spns:
            self.factory.makePOTemplate(distroseries=dapper,
                                        sourcepackagename=spn)
        ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)

        def get_template_spns(series):
            return [
                pot.sourcepackagename
                for pot in getUtility(IPOTemplateSet).getSubset(
                    distroseries=series)
            ]

        edgy = self.factory.makeDistroSeries(distribution=distro, name='edgy')
        edgy_derived = self.factory.makeDistroSeries(
            distribution=ppa.distribution, name='edgy-derived')
        self.factory.makeSourcePackagePublishingHistory(
            archive=ppa,
            distroseries=edgy_derived,
            sourcepackagename=spns[0],
            status=PackagePublishingStatus.PUBLISHED)
        self.factory.makeSourcePackagePublishingHistory(
            archive=edgy.main_archive,
            distroseries=edgy,
            sourcepackagename=spns[1],
            status=PackagePublishingStatus.PUBLISHED)
        self.factory.makeSourcePackagePublishingHistory(
            archive=ppa,
            distroseries=edgy_derived,
            sourcepackagename=spns[2],
            status=PackagePublishingStatus.PENDING)

        self.assertContentEqual(spns, get_template_spns(dapper))
        self.assertContentEqual([], get_template_spns(edgy))
        copy_distroseries_translations(dapper,
                                       edgy,
                                       self.txn,
                                       logging,
                                       published_sources_only=True,
                                       check_archive=ppa,
                                       check_distroseries=edgy_derived)
        self.assertContentEqual([spns[0], spns[2]], get_template_spns(edgy))
 def makeScript(self, test_args):
     script = SuspendBotAccountScript(test_args=test_args)
     script.logger = DevNullLogger()
     script.txn = FakeTransaction()
     return script