From ee85337812d15e93908e3bec3d07c5cdf3a1970f Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Sun, 3 Jan 2016 08:14:37 -0800 Subject: [PATCH 1/5] Require simplejson in setup.py because it's required in jsonutil; require twisted for tests (due to at least test_observer). --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 26651a3..16bb61a 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,10 @@ (os.path.join(u'pyutil', u'data'), [os.path.join(u'pyutil', u'data', u'wordlist.txt')]) ] -install_requires=[u'zbase32 >= 1.0'] +install_requires = [ + u'zbase32 >= 1.0', + u'simplejson >= 2.1.0', +] readmetext_bytes = open(u'README.rst').read() readmetext_unicode = readmetext_bytes.decode('utf-8') @@ -68,6 +71,9 @@ data_files=data_files, extras_require={u'jsonutil': [u'simplejson >= 2.1.0',]}, install_requires=install_requires, + tests_require=[ + u'twisted >= 15.5.0', # for trial (eg user: test_observer) + ], classifiers=trove_classifiers, entry_points = { u'console_scripts': [ From 9c642d26cbab782c80da3bf1efd523b09d5e2100 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Sun, 3 Jan 2016 08:16:10 -0800 Subject: [PATCH 2/5] Add a mock-based passphrase main() test. It fails due to test bug, not app bug... --- pyutil/test/current/test_passphrase.py | 63 ++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 64 insertions(+) create mode 100644 pyutil/test/current/test_passphrase.py diff --git a/pyutil/test/current/test_passphrase.py b/pyutil/test/current/test_passphrase.py new file mode 100644 index 0000000..23f2e33 --- /dev/null +++ b/pyutil/test/current/test_passphrase.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8-with-signature-unix; fill-column: 77 -*- +# -*- indent-tabs-mode: nil -*- + +# This file is part of pyutil; see README.rst for licensing terms. + +import unittest +import argparse +from cStringIO import StringIO + +from mock import patch, call + +from pyutil.scripts import passphrase + + +class Passphrase(unittest.TestCase): + + @patch('argparse.ArgumentParser') + @patch('pyutil.scripts.passphrase.gen_passphrase') + @patch('sys.stdout') + def test_main(self, m_stdout, m_gen_passphrase, m_ArgumentParser): + m_args = m_ArgumentParser.parse_args.return_value + m_args.dictionary = StringIO('alpha\nbeta\n') + m_args.bits = 42 + + m_gen_passphrase.return_value = ('wombat', 43) + + passphrase.main() + + self.assertEqual( + m_ArgumentParser.mock_calls, + [call( + prog='passphrase', + description=('Create a random passphrase by ' + + 'picking a few random words.')), + call().add_argument( + '-d', + '--dictionary', + help=('what file to read a list of words from ' + + "(or omit this option to use passphrase's " + + 'bundled dictionary)'), + type=argparse.FileType('rU'), + metavar="DICT"), + call().add_argument( + 'bits', + help="how many bits of entropy minimum", + type=float, + metavar="BITS"), + call().parse_args(), + + # BUG: Can we remove this layer of specificity? + # Why doesn't m_args remove this layer of specificity? + call().parse_args().__nonzero__(), + call().parse_args().readlines(), + call().parse_args().readlines().__iter__()]) + + self.assertEqual( + m_gen_passphrase.mock_calls, + [call(42, {u'alpha', u'beta'})]) + + self.assertEqual( + m_stdout.mock_calls, + [call.write(u"Your new password is: 'wombat'. " + + "It is worth about 42 bits.\n")]) diff --git a/setup.py b/setup.py index 16bb61a..5181808 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,7 @@ install_requires=install_requires, tests_require=[ u'twisted >= 15.5.0', # for trial (eg user: test_observer) + u'mock >= 1.3.0', ], classifiers=trove_classifiers, entry_points = { From 9186e2f2279ff04e281d07ef531385dca8602ed1 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Sun, 3 Jan 2016 08:47:30 -0800 Subject: [PATCH 3/5] Fix three test_passphrase bugs: use ANY vs FileType, specify parsed args correctly, match observed print behavior. --- pyutil/test/current/test_passphrase.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/pyutil/test/current/test_passphrase.py b/pyutil/test/current/test_passphrase.py index 23f2e33..aabb379 100644 --- a/pyutil/test/current/test_passphrase.py +++ b/pyutil/test/current/test_passphrase.py @@ -4,10 +4,9 @@ # This file is part of pyutil; see README.rst for licensing terms. import unittest -import argparse from cStringIO import StringIO -from mock import patch, call +from mock import patch, call, ANY from pyutil.scripts import passphrase @@ -18,7 +17,7 @@ class Passphrase(unittest.TestCase): @patch('pyutil.scripts.passphrase.gen_passphrase') @patch('sys.stdout') def test_main(self, m_stdout, m_gen_passphrase, m_ArgumentParser): - m_args = m_ArgumentParser.parse_args.return_value + m_args = m_ArgumentParser.return_value.parse_args.return_value m_args.dictionary = StringIO('alpha\nbeta\n') m_args.bits = 42 @@ -38,20 +37,14 @@ def test_main(self, m_stdout, m_gen_passphrase, m_ArgumentParser): help=('what file to read a list of words from ' + "(or omit this option to use passphrase's " + 'bundled dictionary)'), - type=argparse.FileType('rU'), + type=ANY, metavar="DICT"), call().add_argument( 'bits', help="how many bits of entropy minimum", type=float, metavar="BITS"), - call().parse_args(), - - # BUG: Can we remove this layer of specificity? - # Why doesn't m_args remove this layer of specificity? - call().parse_args().__nonzero__(), - call().parse_args().readlines(), - call().parse_args().readlines().__iter__()]) + call().parse_args()]) self.assertEqual( m_gen_passphrase.mock_calls, @@ -60,4 +53,5 @@ def test_main(self, m_stdout, m_gen_passphrase, m_ArgumentParser): self.assertEqual( m_stdout.mock_calls, [call.write(u"Your new password is: 'wombat'. " + - "It is worth about 42 bits.\n")]) + "It is worth about 43 bits."), + call.write('\n')]) From 7f201efdfd1b42caf6ac579f548ab5542147e4e1 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Sun, 3 Jan 2016 09:12:15 -0800 Subject: [PATCH 4/5] Update the test spec and implementation so that stdout contains only the passphrase. --- pyutil/scripts/passphrase.py | 7 +++++-- pyutil/test/current/test_passphrase.py | 12 +++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pyutil/scripts/passphrase.py b/pyutil/scripts/passphrase.py index b69ee7c..2ea2f3a 100644 --- a/pyutil/scripts/passphrase.py +++ b/pyutil/scripts/passphrase.py @@ -4,7 +4,7 @@ # This file is part of pyutil; see README.rst for licensing terms. -import argparse, math, random +import argparse, math, random, sys from pyutil.mathutil import div_ceil @@ -71,4 +71,7 @@ def main(): passphrase, bits = gen_passphrase(args.bits, allwords) - print u"Your new password is: '%s'. It is worth about %.0d bits." % (passphrase, bits) + sys.stdout.write(passphrase) + sys.stdout.write('\n') + + sys.stderr.write(u"This passphrase encodes about %.0d bits.\n" % (bits,)) diff --git a/pyutil/test/current/test_passphrase.py b/pyutil/test/current/test_passphrase.py index aabb379..4ee818e 100644 --- a/pyutil/test/current/test_passphrase.py +++ b/pyutil/test/current/test_passphrase.py @@ -16,7 +16,10 @@ class Passphrase(unittest.TestCase): @patch('argparse.ArgumentParser') @patch('pyutil.scripts.passphrase.gen_passphrase') @patch('sys.stdout') - def test_main(self, m_stdout, m_gen_passphrase, m_ArgumentParser): + @patch('sys.stderr') + def test_main(self, m_stderr, m_stdout, + m_gen_passphrase, m_ArgumentParser): + m_args = m_ArgumentParser.return_value.parse_args.return_value m_args.dictionary = StringIO('alpha\nbeta\n') m_args.bits = 42 @@ -52,6 +55,9 @@ def test_main(self, m_stdout, m_gen_passphrase, m_ArgumentParser): self.assertEqual( m_stdout.mock_calls, - [call.write(u"Your new password is: 'wombat'. " + - "It is worth about 43 bits."), + [call.write(u"wombat"), call.write('\n')]) + + self.assertEqual( + m_stderr.mock_calls, + [call.write(u"This passphrase encodes about 43 bits.\n")]) From f820d45f3e34bcbf26d6e2b56de1a85f8403d509 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Sun, 3 Jan 2016 09:15:36 -0800 Subject: [PATCH 5/5] Switch to string.format string formatting. --- pyutil/scripts/passphrase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyutil/scripts/passphrase.py b/pyutil/scripts/passphrase.py index 2ea2f3a..1f4fff1 100644 --- a/pyutil/scripts/passphrase.py +++ b/pyutil/scripts/passphrase.py @@ -74,4 +74,4 @@ def main(): sys.stdout.write(passphrase) sys.stdout.write('\n') - sys.stderr.write(u"This passphrase encodes about %.0d bits.\n" % (bits,)) + sys.stderr.write(u"This passphrase encodes about {:.0f} bits.\n".format(bits))