Browse Source

Improved command-line processing in 'mmgen-tool'
Added 'tool' argument to test/test.py (WIP)

philemon 10 years ago
parent
commit
7b44b67e0a
5 changed files with 187 additions and 65 deletions
  1. 0 4
      mmgen-tool
  2. 3 3
      mmgen/main_tool.py
  3. 29 41
      mmgen/tool.py
  4. 6 5
      mmgen/util.py
  5. 149 12
      test/test.py

+ 0 - 4
mmgen-tool

@@ -20,10 +20,6 @@
 mmgen-tool:  Perform various Bitcoin-related operations.
              Part of the MMGen suite
 """
-import sys
-print "MMGEN-TOOL was determined to be buggy and is in the process of maintenance."
-print "Please check back soon!"
-sys.exit(3)
 
 from mmgen.main import launch
 launch("tool")

+ 3 - 3
mmgen/main_tool.py

@@ -62,9 +62,9 @@ if cmd_args and cmd_args[0] == '--help':
 	tool.tool_usage(g.prog_name, command)
 	sys.exit(0)
 
-args = tool.process_args(g.prog_name, command, cmd_args)
+args,kwargs = tool.process_args(g.prog_name, command, cmd_args)
 
+#msgrepr(args,kwargs)
 tool.opts = opts
 
-#print command + "(" + ", ".join(args) + ")"
-eval("tool." + command + "(" + ", ".join(args) + ")")
+tool.__dict__[command](*args,**kwargs)

+ 29 - 41
mmgen/tool.py

@@ -155,65 +155,53 @@ def tool_usage(prog_name, command):
 	print "USAGE: '%s %s%s'" % (prog_name, command,
 		(" "+" ".join(commands[command]) if commands[command] else ""))
 
-def process_args(prog_name, command, uargs):
-	cargs = commands[command]
-	cargs_req = [[i.split(" [")[0],i.split(" [")[1][:-1]]
-		for i in cargs if "=" not in i]
-	cargs_nam = dict([[
+def process_args(prog_name, command, cmd_args):
+	c_args = [[i.split(" [")[0],i.split(" [")[1][:-1]]
+		for i in commands[command] if "=" not in i]
+	c_kwargs = dict([[
 			i.split(" [")[0],
 			[i.split(" [")[1].split("=")[0], i.split(" [")[1].split("=")[1][:-1]]
-		] for i in cargs if "=" in i])
-	uargs_req = [i for i in uargs if "=" not in i]
-	uargs_nam = dict([i.split("=") for i in uargs if "=" in i])
+		] for i in commands[command] if "=" in i])
+	u_args = cmd_args[:len(c_args)]
+	u_kwargs = dict([i.split("=") for i in cmd_args[len(c_args):]])
 
-#	print cargs_req; print cargs_nam; print uargs_req; print uargs_nam; sys.exit()
+#	print c_args; print c_kwargs; print u_args; print u_kwargs; sys.exit()
 
-	n = len(cargs_req)
-	if len(uargs_req) != n:
+	if len(u_args) != len(c_args):
 		tool_usage(prog_name, command)
 		sys.exit(1)
 
-	for a in uargs_nam.keys():
-		if a not in cargs_nam.keys():
-			print "'%s' invalid named argument" % a
-			sys.exit(1)
+	if set(u_kwargs) > set(c_kwargs):
+		print "Invalid named argument"
+		sys.exit(1)
 
-	def test_type(arg_type,arg,name=""):
+	def convert_type(arg,arg_name,arg_type):
 		try:
-			t = type(eval(arg))
-			assert(t == eval(arg_type))
+			return __builtins__[arg_type](arg)
 		except:
 			print "'%s': Invalid argument for argument %s ('%s' required)" % \
-				(arg, name, arg_type)
+				(arg, arg_name, arg_type)
 			sys.exit(1)
-		return True
 
-	ret = []
-
-	def normalize_arg(arg, arg_type):
+	def convert_to_bool_maybe(arg, arg_type):
 		if arg_type == "bool":
-			if arg.lower() in ("true","yes","1","on"): return "True"
-			if arg.lower() in ("false","no","0","off"): return "False"
+			if arg.lower() in ("true","yes","1","on"): return True
+			if arg.lower() in ("false","no","0","off"): return False
 		return arg
 
-	for i in range(len(cargs_req)):
-		arg_type = cargs_req[i][1]
-		arg = normalize_arg(uargs_req[i], arg_type)
-		if arg_type == "str":
-			ret.append('"%s"' % (arg))
-		elif test_type(arg_type, arg, "#"+str(i+1)):
-			ret.append('%s' % (arg))
-
-	for k in uargs_nam.keys():
-		arg_type = cargs_nam[k][0]
-		arg = normalize_arg(uargs_nam[k], arg_type)
-		if arg_type == "str":
-			ret.append('%s="%s"' % (k, arg))
-		elif test_type(arg_type, arg, "'"+k+"'"):
-			ret.append('%s=%s' % (k, arg))
+	args = []
+	for i in range(len(c_args)):
+		arg_type = c_args[i][1]
+		arg = convert_to_bool_maybe(u_args[i], arg_type)
+		args.append(convert_type(arg,c_args[i][0],arg_type))
 
-	return ret
+	kwargs = {}
+	for k in u_kwargs:
+		arg_type = c_kwargs[k][0]
+		arg = convert_to_bool_maybe(u_kwargs[k], arg_type)
+		kwargs[k] = convert_type(arg,k,arg_type)
 
+	return args,kwargs
 
 # Individual commands
 

+ 6 - 5
mmgen/util.py

@@ -316,7 +316,7 @@ def write_to_stdout(data, what, confirm=True):
 	sys.stdout.write(data)
 
 
-def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True):
+def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True,silent=False):
 
 	if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
 
@@ -333,13 +333,13 @@ def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=
 					msg("Not overwriting file at user request")
 					return False
 		else:
-			msg("Overwriting file '%s'" % outfile)
+			if not silent: msg("Overwriting file '%s'" % outfile)
 
 	f = open_file_or_exit(outfile,'wb')
 	try:
 		f.write(data)
 	except:
-		msg("Failed to write %s to file '%s'" % (what,outfile))
+		if not silent: msg("Failed to write %s to file '%s'" % (what,outfile))
 		sys.exit(2)
 	f.close
 
@@ -549,9 +549,10 @@ def get_lines_from_file(infile,what="",trim_comments=False):
 	return remove_comments(lines) if trim_comments else lines
 
 
-def get_data_from_file(infile,what="data",dash=False):
+def get_data_from_file(infile,what="data",dash=False,silent=False):
 	if dash and infile == "-": return sys.stdin.read()
-	qmsg("Getting %s from file '%s'" % (what,infile))
+	if not silent:
+		qmsg("Getting %s from file '%s'" % (what,infile))
 	f = open_file_or_exit(infile,'rb')
 	data = f.read()
 	f.close()

+ 149 - 12
test/test.py

@@ -45,23 +45,55 @@ cmd_data = OrderedDict([
 	['keyaddrgen',    (1,'key-address file generation', [[["mmdat"],1]])],
 	['txsign_keyaddr',(1,'transaction signing with key-address file', [[["akeys.mmenc","raw"],1]])],
 
-	['walletgen2',(2,'wallet generation (2), 128-bit seed (WIP)',     [])],
+	['walletgen2',(2,'wallet generation (2)',     [])],
+#	['walletgen2',(2,'wallet generation (2), 128-bit seed (WIP)',     [])],
 	['addrgen2',  (2,'address generation (2)',    [[["mmdat"],2]])],
 	['txcreate2', (2,'transaction creation (2)',  [[["addrs"],2]])],
 	['txsign2',   (2,'transaction signing, two transactions',[[["mmdat","raw"],1],[["mmdat","raw"],2]])],
-	['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])],
+	['export_mnemonic2', (2,'seed export to mmwords format (2)',[[["mmdat"],2]])],
+#	['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])],
 
 	['walletgen3',(3,'wallet generation (3)',         [])],
 	['addrgen3',  (3,'address generation (3)',        [[["mmdat"],3]])],
 	['txcreate3', (3,'tx creation with inputs and outputs from two wallets', [[["addrs"],1],[["addrs"],3]])],
 	['txsign3',   (3,'tx signing with inputs and outputs from two wallets',[[["mmdat"],1],[["mmdat","raw"],3]])],
 
-	['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])],
+	['walletgen4',(4,'wallet generation (4) (brainwallet)', [])],
+#	['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])],
 	['addrgen4',  (4,'address generation (4)',              [[["mmdat"],4]])],
 	['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, plus non-MMGen inputs and outputs', [[["addrs"],1],[["addrs"],2],[["addrs"],3],[["addrs"],4]])],
 	['txsign4',   (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[["mmincog"],1],[["mmwords"],2],[["mmdat"],3],[["mmbrain","raw"],4]])],
 ])
 
+tool_cmd_data = OrderedDict([
+	['strtob58',   (10, '',   [])],
+	['b58tostr',   (10, '',   [[["strtob58.in","strtob58.out"],10]])],
+	['hextob58',   (10, '',   [])],
+	['b58tohex',   (10, '',   [[["hextob58.in","hextob58.out"],10]])],
+# 	"b58randenc":   [],
+# 	"randhex":      ['nbytes [int=32]'],
+# 	"randwif":      ['compressed [bool=False]'],
+# 	"randpair":     ['compressed [bool=False]'],
+# 	"wif2hex":      ['<wif> [str]', 'compressed [bool=False]'],
+# 	"wif2addr":     ['<wif> [str]', 'compressed [bool=False]'],
+# 	"hex2wif":      ['<private key in hex format> [str]', 'compressed [bool=False]'],
+# 	"hexdump":      ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]'],
+# 	"unhexdump":    ['<infile> [str]'],
+# 	"hex2mn":       ['<hexadecimal string> [str]','wordlist [str="electrum"]'],
+# 	"mn2hex":       ['<mnemonic> [str]', 'wordlist [str="electrum"]'],
+# 	"b32tohex":     ['<b32 num> [str]'],
+# 	"hextob32":     ['<hex num> [str]'],
+# 	"mn_rand128":   ['wordlist [str="electrum"]'],
+# 	"mn_rand192":   ['wordlist [str="electrum"]'],
+# 	"mn_rand256":   ['wordlist [str="electrum"]'],
+# 	"mn_stats":     ['wordlist [str="electrum"]'],
+# 	"mn_printlist": ['wordlist [str="electrum"]'],
+# 	"id8":          ['<infile> [str]'],
+# 	"id6":          ['<infile> [str]'],
+# 	"str2id6":      ['<string (spaces are ignored)> [str]'],
+])
+
+
 utils = {
 	'check_deps': 'check dependencies for specified command',
 	'clean':      'clean specified tmp dir(s) 1, 2, 3 or 4 (no arg = all dirs)',
@@ -69,6 +101,16 @@ utils = {
 
 addrs_per_wallet = 8
 cfgs = {
+	'10': {
+		'name':          "test the tool utility",
+		'enc_passwd':    "Ten Satoshis",
+		'tmpdir':        "test/tmp10",
+		'dep_generators':  {
+			'strtob58.out': "strtob58",
+			'strtob58.in':  "strtob58",
+		},
+
+	},
 	'6': {
 		'name':            "reference wallet check",
 		'bw_passwd':       "abc",
@@ -156,6 +198,7 @@ cfgs = {
 
 from binascii import hexlify
 def getrand(n): return int(hexlify(os.urandom(n)),16)
+def getrandhex(n): return hexlify(os.urandom(n))
 
 # total of two outputs must be < 10 BTC
 for k in cfgs.keys():
@@ -342,12 +385,17 @@ def verify_checksum_or_exit(checksum,chk):
 		sys.exit(1)
 	vmsg(green("Checksums match: %s") % (cyan(chk)))
 
+def get_rand_printable_chars(num_chars,no_punc=False):
+	return [chr(ord(i)%94+33) for i in list(os.urandom(num_chars))]
+
+
 class MMGenExpect(object):
 
 	def __init__(self,name,mmgen_cmd,cmd_args=[],env=env):
 		if not 'system' in opts:
 			mmgen_cmd = os.path.join(os.curdir,mmgen_cmd)
 		desc = cmd_data[name][1]
+		if not desc: desc = name
 		if verbose or exact_output:
 			sys.stderr.write(
 				green("Testing %s\nExecuting " % desc) +
@@ -365,7 +413,7 @@ class MMGenExpect(object):
 		my_expect(self.p,p,'c')
 
 	def usr_rand(self,num_chars):
-		rand_chars = [chr(ord(i)%94+33) for i in list(os.urandom(num_chars))]
+		rand_chars = get_rand_printable_chars(num_chars)
 		my_expect(self.p,'symbols left: ','x')
 		try:
 			vmsg_r("SEND ")
@@ -429,7 +477,10 @@ class MMGenExpect(object):
 	def readline(self):
 		return self.p.readline()
 
-	def read(self,n):
+	def readlines(self):
+		return [l.rstrip()+"\n" for l in self.p.readlines()]
+
+	def read(self,n=None):
 		return self.p.read(n)
 
 
@@ -513,7 +564,7 @@ def do_cmd(ts,cmd):
 	global cfg
 	cfg = cfgs[str(cmd_data[cmd][0])]
 
-	MMGenTestSuite.__dict__[cmd](*([ts,cmd] + al))
+	ts.__class__.__dict__[cmd](*([ts,cmd] + al))
 
 
 hincog_bytes   = 1024*1024
@@ -591,7 +642,6 @@ def ok():
 		sys.stderr.write(green("OK\n"))
 	else: msg(" OK")
 
-
 class MMGenTestSuite(object):
 
 	def __init__(self):
@@ -1004,10 +1054,85 @@ class MMGenTestSuite(object):
 		t.written_to_file("Signed transaction")
 		ok()
 
-# main()
-ts = MMGenTestSuite()
-start_time = int(time.time())
 
+def write_to_tmpfile(fn,data):
+	write_to_file(os.path.join(cfg['tmpdir'],fn),data,{},silent=True)
+
+def read_from_tmpfile(fn):
+	from mmgen.util import get_data_from_file
+	return get_data_from_file(os.path.join(cfg['tmpdir'],fn),silent=True)
+
+def read_from_file(fn):
+	from mmgen.util import get_data_from_file
+	return get_data_from_file(fn,silent=True)
+
+class MMGenToolTestSuite(object):
+
+	def __init__(self):
+		global cmd_data,tool_cmd_data
+		cmd_data = tool_cmd_data
+		pass
+
+	def clean(self,name):
+		cleandir(cfgs['10']['tmpdir'])
+
+	def cmd(self,name,tool_args):
+		mk_tmpdir(cfg)
+		t = MMGenExpect(name,"mmgen-tool", ["-d",cfg['tmpdir']] + tool_args)
+		return t.read()
+
+	def cmd_to_tmpfile(self,name,tool_args,tmpfile):
+		ret = self.cmd(name,tool_args)
+		if ret:
+			write_to_tmpfile(tmpfile,ret)
+			ok()
+
+	def strtob58(self,name):
+		s = "".join(get_rand_printable_chars(15))
+		write_to_tmpfile('strtob58.in',s)
+		self.cmd_to_tmpfile(name,["strtob58",s],'strtob58.out')
+
+	def b58tostr(self,name,f1,f2):
+		idata = read_from_file(f1)
+		odata = read_from_file(f2)[:-2]
+		res = self.cmd(name,["b58tostr",odata])[:-2]
+		if res == idata: ok()
+		else: errmsg(red("Error"))
+
+	def hextob58(self,name):
+		hexnum = getrandhex(32)
+		write_to_tmpfile('hextob58.in',hexnum)
+		self.cmd_to_tmpfile(name,["hextob58",hexnum],'hextob58.out')
+
+	def b58tohex(self,name,f1,f2):
+		idata = read_from_file(f1)
+		odata = read_from_file(f2)[:-2]
+		res = self.cmd(name,["b58tohex",odata])[:-2]
+		if res == idata: ok()
+		else: errmsg(red("Error"))
+# 	"b58randenc":   [],
+# 	"randhex":      ['nbytes [int=32]'],
+# 	"randwif":      ['compressed [bool=False]'],
+# 	"randpair":     ['compressed [bool=False]'],
+# 	"wif2hex":      ['<wif> [str]', 'compressed [bool=False]'],
+# 	"wif2addr":     ['<wif> [str]', 'compressed [bool=False]'],
+# 	"hex2wif":      ['<private key in hex format> [str]', 'compressed [bool=False]'],
+# 	"hexdump":      ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]'],
+# 	"unhexdump":    ['<infile> [str]'],
+# 	"hex2mn":       ['<hexadecimal string> [str]','wordlist [str="electrum"]'],
+# 	"mn2hex":       ['<mnemonic> [str]', 'wordlist [str="electrum"]'],
+# 	"b32tohex":     ['<b32 num> [str]'],
+# 	"hextob32":     ['<hex num> [str]'],
+# 	"mn_rand128":   ['wordlist [str="electrum"]'],
+# 	"mn_rand192":   ['wordlist [str="electrum"]'],
+# 	"mn_rand256":   ['wordlist [str="electrum"]'],
+# 	"mn_stats":     ['wordlist [str="electrum"]'],
+# 	"mn_printlist": ['wordlist [str="electrum"]'],
+# 	"id8":          ['<infile> [str]'],
+# 	"id6":          ['<infile> [str]'],
+# 	"str2id6":      ['<string (spaces are ignored)> [str]'],
+
+# main()
 if pause:
 	import termios,atexit
 	fd = sys.stdin.fileno()
@@ -1016,20 +1141,24 @@ if pause:
 		termios.tcsetattr(fd, termios.TCSADRAIN, old)
 	atexit.register(at_exit)
 
+start_time = int(time.time())
+
 try:
-	if cmd_args:
+	if cmd_args and cmd_args[0] != "tool":
 		arg1 = cmd_args[0]
 		if arg1 in utils:
 			MMGenTestSuite.__dict__[arg1](ts,arg1,cmd_args[1:])
 			sys.exit()
 		elif arg1 in meta_cmds:
+			ts = MMGenTestSuite()
 			if len(cmd_args) == 1:
 				for cmd in meta_cmds[arg1][1]:
 					check_needs_rerun(cmd,build=True,force_delete=True)
 			else:
 				msg("Only one meta command may be specified")
 				sys.exit(1)
-		elif arg1 in cmd_data:
+		elif arg1 in cmd_data.keys() + tool_cmd_data.keys():
+			ts = MMGenTestSuite() if arg1 in cmd_data else MMGenToolTestSuite()
 			if len(cmd_args) == 1:
 				check_needs_rerun(arg1,build=True)
 			else:
@@ -1039,6 +1168,14 @@ try:
 			errmsg("%s: unrecognized command" % arg1)
 			sys.exit(1)
 	else:
+		if cmd_args: # tool
+			if len(cmd_args) != 1:
+				msg("Only one command may be specified")
+				sys.exit(1)
+			ts = MMGenToolTestSuite()
+		else:
+			ts = MMGenTestSuite()
+
 		ts.clean("clean")
 		for cmd in cmd_data:
 			do_cmd(ts,cmd)