Browse Source

Add tests for bash completion; fix Unicode bug that turned up

Note that argcomplete is also now required.
tags/nilmdb-2.0.0
Jim Paris 3 years ago
parent
commit
9126980ed4
4 changed files with 83 additions and 12 deletions
  1. +5
    -11
      nilmdb/cmdline/cmdline.py
  2. +1
    -0
      requirements.txt
  3. +0
    -1
      setup.py
  4. +77
    -0
      tests/test_cmdline.py

+ 5
- 11
nilmdb/cmdline/cmdline.py View File

@@ -12,10 +12,7 @@ import argparse
from argparse import ArgumentDefaultsHelpFormatter as def_form
import signal

try: # pragma: no cover
import argcomplete
except ImportError: # pragma: no cover
argcomplete = None
import argcomplete

# Valid subcommands. Defined in separate files just to break
# things up -- they're still called with Cmdline as self.
@@ -40,7 +37,7 @@ class JimArgumentParser(argparse.ArgumentParser):
self.print_usage(sys.stderr)
self.exit(2, sprintf("error: %s\n", message))

class Complete(object): # pragma: no cover
class Complete(object):
# Completion helpers, for using argcomplete (see
# extras/nilmtool-bash-completion.sh)
def escape(self, s):
@@ -80,10 +77,6 @@ class Complete(object): # pragma: no cover
if not path:
return []
results = []
# prefix comes in as UTF-8, but results need to be Unicode,
# weird. Still doesn't work in all cases, but that's bugs in
# argcomplete.
prefix = nilmdb.utils.str.decode(prefix)
for (k,v) in client.stream_get_metadata(path).items():
kv = self.escape(k + '=' + v)
if kv.startswith(prefix):
@@ -98,6 +91,7 @@ class Cmdline(object):
self.def_url = os.environ.get("NILMDB_URL", "http://localhost/nilmdb/")
self.subcmd = {}
self.complete = Complete()
self.complete_output_stream = None # overridden by test suite

def arg_time(self, toparse):
"""Parse a time string argument"""
@@ -153,8 +147,8 @@ class Cmdline(object):

# Run parser
self.parser_setup()
if argcomplete: # pragma: no cover
argcomplete.autocomplete(self.parser)
argcomplete.autocomplete(self.parser, exit_method=sys.exit,
output_stream=self.complete_output_stream)
self.args = self.parser.parse_args(self.argv)

# Run arg verify handler if there is one


+ 1
- 0
requirements.txt View File

@@ -1,3 +1,4 @@
argcomplete>=1.10.0
CherryPy>=18.1.2
coverage>=4.5.4
cython>=0.29.13


+ 0
- 1
setup.py View File

@@ -6,7 +6,6 @@
# Then just package it up:
# python setup.py sdist

import traceback
import sys
import os
from setuptools import setup


+ 77
- 0
tests/test_cmdline.py View File

@@ -360,6 +360,12 @@ class TestCmdline(object):
self.ok("metadata /newton/raw --update "
"v_scale=1.234")

# unicode
self.ok("metadata /newton/raw --set "
"a_๐“ด๐“ฎ๐”‚=value a_key=๐“ฟ๐“ช๐“ต๐“พ๐“ฎ a_๐—ธ๐—ฒ๐˜†=๐˜ƒ๐—ฎ๐—น๐˜‚๐—ฒ")
self.ok("metadata /newton/raw --get")
self.match("a_key=๐“ฟ๐“ช๐“ต๐“พ๐“ฎ\na_๐“ด๐“ฎ๐”‚=value\na_๐—ธ๐—ฒ๐˜†=๐˜ƒ๐—ฎ๐—น๐˜‚๐—ฒ\n")

# various parsing tests
self.ok("metadata /newton/raw --update foo=")
self.fail("metadata /newton/raw --update =bar")
@@ -1166,3 +1172,74 @@ class TestCmdline(object):

server_stop()
server_start()

def test_05b_completion(self):
# Test bash completion. This depends on some data put in the DB by
# earlier tests, so the execution order is important.
def complete(line, expect="<unspecified>"):
# set env vars
env = {
'_ARGCOMPLETE': '1',
'COMP_LINE': line,
'COMP_POINT': str(len(line)),
'COMP_TYPE': '8',
'NILMDB_URL': "http://localhost:32180/",
}
for (k, v) in env.items():
os.environ[k] = v

# create pipe for completion output
output = io.BytesIO()

# ensure argcomplete won't mess with any FDs
def fake_fdopen(fd, mode):
return io.BytesIO()
old_fdopen = os.fdopen
os.fdopen = fake_fdopen

# run cli
cmdline = nilmdb.cmdline.Cmdline([])
cmdline.complete_output_stream = output
try:
cmdline.run()
sys.exit(0)
except SystemExit as e:
exitcode = e.code
eq_(exitcode, 0)

# clean up
os.fdopen = old_fdopen
for (k, v) in env.items():
del os.environ[k]

# read completion output
comp = output.getvalue()

# replace completion separators with commas, for clarity
cleaned = comp.replace(b'\x0b', b',').decode('utf-8')

# expect the given match or prefix
if expect.endswith('*'):
if not cleaned.startswith(expect[:-1]):
raise AssertionError(("completions:\n '%s'\n"
"don't start with:\n '%s'") %
(cleaned, expect[:-1]))
else:
if cleaned != expect:
raise AssertionError(("completions:\n '%s'\n"
"don't match:\n '%s'") %
(cleaned, expect))

complete("nilmtool -u ", "")
complete("nilmtool list ", "-h,--help,-E,--ext*")
complete("nilmtool list --st", "--start ")
complete("nilmtool list --start ", "")
complete("nilmtool list /", "/newton/prep,/newton/raw*")
complete("nilmtool create /foo int3", "int32_1,int32_2*")
complete("nilmtool metadata /newton/raw --get a",
"a_๐“ด๐“ฎ๐”‚,a_key,a_๐—ธ๐—ฒ๐˜†")
complete("nilmtool metadata /newton/raw --set a",
"a_๐“ด๐“ฎ๐”‚=value,a_key=๐“ฟ๐“ช๐“ต๐“พ๐“ฎ,a_๐—ธ๐—ฒ๐˜†=๐˜ƒ๐—ฎ๐—น๐˜‚๐—ฒ")
complete("nilmtool metadata /newton/raw --set a_๐—ธ", "a_๐—ธ๐—ฒ๐˜†=๐˜ƒ๐—ฎ๐—น๐˜‚๐—ฒ ")
complete("nilmtool metadata '' --set a", "")
self.run("list")

Loadingโ€ฆ
Cancel
Save