From 11d4015f71111fd4addc416ba2c9c1fa2b5cba1f Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 20 Jun 2018 08:18:46 -0700 Subject: [PATCH] Fixes and tests to supybot-plugin-create; modernize the plugin template (#1340) * supybot-plugin-create: compactify import statements in the template * supybot-plugin-create: prefer importlib over imp on Python >= 3.4 The imp module is deprecated as of Python 3.4[1], with importlib being the successor. However, importlib is only available in Python 2.7+ and 3.1+, so we should still use a fallback. [1]: https://docs.python.org/3.6/library/imp.html * test: add test cases for supybot-plugin-create * -plugin-create: fix errors when only a subset of args are given * -plugin-create: rename --real-name to --author/-a These days, working under a pseudonym or alias is not unheard of, so putting emphasis on real names feels somewhat out of place. * -plugin-create: add -d as an alias to --desc for consistency --- scripts/supybot-plugin-create | 34 +++++------ test/test_plugin_create.py | 106 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 test/test_plugin_create.py diff --git a/scripts/supybot-plugin-create b/scripts/supybot-plugin-create index e76f9661e..0ceed7d72 100644 --- a/scripts/supybot-plugin-create +++ b/scripts/supybot-plugin-create @@ -92,11 +92,8 @@ license = license.lstrip() pluginTemplate = ''' %s -import supybot.utils as utils +from supybot import utils, plugins, ircutils, callbacks from supybot.commands import * -import supybot.plugins as plugins -import supybot.ircutils as ircutils -import supybot.callbacks as callbacks try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('%s') @@ -120,8 +117,7 @@ Class = %s configTemplate = ''' %s -import supybot.conf as conf -import supybot.registry as registry +from supybot import conf, registry try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('%s') @@ -157,8 +153,9 @@ __init__Template = ''' %s: %s """ +import sys import supybot -import supybot.world as world +from supybot import world # Use this for the version of this plugin. You may wish to put a CVS keyword # in here if you're keeping the plugin in CVS or some similar system. @@ -176,7 +173,10 @@ __url__ = '' from . import config from . import plugin -from imp import reload +if sys.version_info >= (3, 4): + from importlib import reload +else: + from imp import reload # In case we're being reloaded. reload(config) reload(plugin) @@ -219,20 +219,15 @@ def main(): help='sets the name for the plugin.') parser.add_option('-t', '--thread', action='store_true', dest='threaded', help='makes the plugin threaded.') - parser.add_option('', '--real-name', action='store', dest='realName', - help='Determines what real name the copyright is ' + parser.add_option('-a', '--author', '--real-name', action='store', + dest='realName', help='Determines who the copyright is ' 'assigned to.') - parser.add_option('', '--desc', action='store', dest='desc', + parser.add_option('-d', '--desc', action='store', dest='desc', help='Short description of plugin.') (options, args) = parser.parse_args() if options.name: name = options.name - if options.threaded: - threaded = True - else: - threaded = False - if options.realName: - realName = options.realName + threaded = options.threaded else: name = something('What should the name of the plugin be?') if name.endswith('.py'): @@ -255,8 +250,11 @@ def main(): print() threaded = yn('Does your plugin need to be threaded?') + if options.realName: + realName = options.realName + else: realName = something(textwrap.dedent(""" - What is your real name, so I can fill in the copyright and license + What is your name, so I can fill in the copyright and license appropriately? """).strip()) diff --git a/test/test_plugin_create.py b/test/test_plugin_create.py new file mode 100644 index 000000000..73e421740 --- /dev/null +++ b/test/test_plugin_create.py @@ -0,0 +1,106 @@ +### +# Copyright (c) 2018, James Lu +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +import supybot +from supybot.test import * + +import subprocess +import unittest +import os +import shutil +import compileall + +TEST_PLUGIN_NAME = "TestPlugin" + +class PluginCreateTestCase(SupyTestCase): + + @staticmethod + def _communicate(proc, text): + outs, errs = proc.communicate(input=text) + + supybot.log.info("testPluginCreate: supybot-plugin-create outs:") + for line in outs.splitlines(): + supybot.log.info(" %s", line.decode()) + supybot.log.info("testPluginCreate: supybot-plugin-create errs:") + for line in errs.splitlines(): + supybot.log.info(" %s", line.decode()) + + def _makeplugin(self): + proc = subprocess.Popen(['supybot-plugin-create'], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # In order: plugin name, threaded?, author, use Supybot license?, description + cmdinput = TEST_PLUGIN_NAME.encode() + b""" +n +Test Case Runner +y +Dummy test plugin +""" + self._communicate(proc, cmdinput) + + def testPluginCreate(self): + tmpdir = conf.supybot.directories.data.tmp() + curdir = os.getcwd() + try: + os.chdir(tmpdir) + if TEST_PLUGIN_NAME in os.listdir('.'): + supybot.log.info("testPluginCreate: Removing old TestPlugin directory") + shutil.rmtree(TEST_PLUGIN_NAME) + + self._makeplugin() + + self.assertIn(TEST_PLUGIN_NAME, os.listdir('.')) + + # Make sure that out generated plugin is valid + compileall.compile_dir(TEST_PLUGIN_NAME) + + finally: + os.chdir(curdir) + +class PluginCreateNoninteractiveTestCase(PluginCreateTestCase): + def _makeplugin(self): + with open(os.devnull, 'w') as devnull: # Compat with Python < 3.3 + retcode = subprocess.call(['supybot-plugin-create', '-n', TEST_PLUGIN_NAME, + '--author=skynet', '--desc=Some description'], + stdin=devnull) + self.assertFalse(retcode) # Check that the return code is 0 + +class PluginCreatePartialArgsTestCase(PluginCreateTestCase): + def _makeplugin(self): + # We passed in a subset of args, so the script should only prompt for the + # ones not given + proc = subprocess.Popen(['supybot-plugin-create', '-n', TEST_PLUGIN_NAME], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + # In order: threaded?, author, use Supybot license?, description + cmdinput = TEST_PLUGIN_NAME.encode() + b""" +Test Case Runner +y +Dummy test plugin +""" + self._communicate(proc, cmdinput)