Add some go testing infrastructure

This commit is contained in:
Kovid Goyal 2022-08-16 09:22:12 +05:30
parent 3a21605b05
commit 1325844539
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 102 additions and 10 deletions

View File

@ -3,10 +3,12 @@
import importlib import importlib
import os import os
import shlex
import shutil import shutil
import subprocess
import sys import sys
import unittest import unittest
from typing import Callable, Generator, Iterator, NoReturn, Sequence, Set from typing import Callable, Generator, Iterator, List, NoReturn, Sequence, Set
def contents(package: str) -> Iterator[str]: def contents(package: str) -> Iterator[str]:
@ -91,24 +93,75 @@ def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
raise SystemExit(1) raise SystemExit(1)
def find_testable_go_packages() -> Set[str]:
ans = set()
base = os.getcwd()
for (dirpath, dirnames, filenames) in os.walk(base):
for f in filenames:
if f.endswith('_test.go'):
q = os.path.relpath(dirpath, base)
ans.add(q)
return ans
def go_exe() -> str:
return shutil.which('go') or ''
def create_go_filter(packages: List[str], *names: str) -> str:
go = go_exe()
if not go:
return ''
all_tests = set()
for line in subprocess.check_output('go test -list .'.split() + packages).decode().splitlines():
if line.startswith('Test'):
all_tests.add(line[4:])
tests = set(names) & all_tests
return '|'.join(tests)
def run_go(packages: List[str], names: str) -> None:
go = go_exe()
if not go:
print('Skipping Go tests as go exe not found', file=sys.stderr)
return
if not packages:
print('Skipping Go tests as go source files not availabe', file=sys.stderr)
return
cmd = 'go test -v'.split()
if names:
cmd.extend(('-run', names))
cmd += packages
print(shlex.join(cmd))
os.execl(go, *cmd)
def run_tests() -> None: def run_tests() -> None:
import argparse import argparse
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
'name', nargs='*', default=[], 'name', nargs='*', default=[],
help='The name of the test to run, for e.g. linebuf corresponds to test_linebuf. Can be specified multiple times') help='The name of the test to run, for e.g. linebuf corresponds to test_linebuf. Can be specified multiple times.')
parser.add_argument('--verbosity', default=4, type=int, help='Test verbosity') parser.add_argument('--verbosity', default=4, type=int, help='Test verbosity')
parser.add_argument('--module', default='', help='Name of a test module to restrict to. For example: ssh') parser.add_argument('--module', default='', help='Name of a test module to restrict to. For example: ssh.'
' For Go tests this is the name of a package, for example: tools/cli')
args = parser.parse_args() args = parser.parse_args()
if args.name and args.name[0] in ('type-check', 'type_check', 'mypy'): if args.name and args.name[0] in ('type-check', 'type_check', 'mypy'):
type_check() type_check()
tests = find_all_tests() tests = find_all_tests()
go_packages = find_testable_go_packages()
go_filter_spec = ''
if args.module: if args.module:
tests = filter_tests_by_module(tests, args.module) tests = filter_tests_by_module(tests, args.module)
if not tests._tests: go_packages &= {args.module}
if not tests._tests and not go_packages:
raise SystemExit('No test module named %s found' % args.module) raise SystemExit('No test module named %s found' % args.module)
go_pkg_args = [f'kitty/{x}' for x in go_packages]
if args.name: if args.name:
tests = filter_tests_by_name(tests, *args.name) tests = filter_tests_by_name(tests, *args.name)
if not tests._tests: go_filter_spec = create_go_filter(go_pkg_args, *args.name)
if not tests._tests and not go_filter_spec:
raise SystemExit('No test named %s found' % args.name) raise SystemExit('No test named %s found' % args.name)
run_cli(tests, args.verbosity) run_cli(tests, args.verbosity)
run_go(go_pkg_args, go_filter_spec)

View File

@ -32,6 +32,7 @@ def main() -> None:
launcher_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty', 'launcher') launcher_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty', 'launcher')
path = f'{launcher_dir}{os.pathsep}{path}' path = f'{launcher_dir}{os.pathsep}{path}'
PYTHON_FOR_TYPE_CHECK = shutil.which('python') or shutil.which('python3') or '' PYTHON_FOR_TYPE_CHECK = shutil.which('python') or shutil.which('python3') or ''
gohome = os.path.expanduser('~/go')
with TemporaryDirectory() as tdir, env_vars( with TemporaryDirectory() as tdir, env_vars(
PYTHONWARNINGS='error', HOME=tdir, USERPROFILE=tdir, PATH=path, PYTHONWARNINGS='error', HOME=tdir, USERPROFILE=tdir, PATH=path,
XDG_CONFIG_HOME=os.path.join(tdir, '.config'), XDG_CONFIG_HOME=os.path.join(tdir, '.config'),
@ -40,6 +41,8 @@ def main() -> None:
XDG_CACHE_HOME=os.path.join(tdir, '.cache'), XDG_CACHE_HOME=os.path.join(tdir, '.cache'),
PYTHON_FOR_TYPE_CHECK=PYTHON_FOR_TYPE_CHECK, PYTHON_FOR_TYPE_CHECK=PYTHON_FOR_TYPE_CHECK,
): ):
if os.path.isdir(gohome):
os.symlink(gohome, os.path.join(tdir, os.path.basename(gohome)))
m = importlib.import_module('kitty_tests.main') m = importlib.import_module('kitty_tests.main')
getattr(m, 'run_tests')() getattr(m, 'run_tests')()

View File

@ -83,6 +83,7 @@ func format_line_with_indent(output io.Writer, text string, indent string, scree
fmt.Fprint(output, indent) fmt.Fprint(output, indent)
in_escape := 0 in_escape := 0
var current_word strings.Builder var current_word strings.Builder
var escapes strings.Builder
print_word := func(r rune) { print_word := func(r rune) {
w := runewidth.StringWidth(current_word.String()) w := runewidth.StringWidth(current_word.String())
@ -94,8 +95,14 @@ func format_line_with_indent(output io.Writer, text string, indent string, scree
current_word.Reset() current_word.Reset()
current_word.WriteString(s) current_word.WriteString(s)
} }
fmt.Fprint(output, current_word.String()) if escapes.Len() > 0 {
output.Write([]byte(escapes.String()))
escapes.Reset()
}
if current_word.Len() > 0 {
output.Write([]byte(current_word.String()))
current_word.Reset() current_word.Reset()
}
if r > 0 { if r > 0 {
current_word.WriteRune(r) current_word.WriteRune(r)
} }
@ -113,7 +120,7 @@ func format_line_with_indent(output io.Writer, text string, indent string, scree
if (in_escape == 2 && r == 'm') || (in_escape == 3 && r == '\\' && text[i-1] == 0x1b) { if (in_escape == 2 && r == 'm') || (in_escape == 3 && r == '\\' && text[i-1] == 0x1b) {
in_escape = 0 in_escape = 0
} }
fmt.Fprint(output, string(r)) escapes.WriteRune(r)
continue continue
} }
if r == 0x1b { if r == 0x1b {
@ -121,7 +128,7 @@ func format_line_with_indent(output io.Writer, text string, indent string, scree
if current_word.Len() != 0 { if current_word.Len() != 0 {
print_word(0) print_word(0)
} }
fmt.Fprint(output, string(r)) escapes.WriteRune(r)
continue continue
} }
if current_word.Len() != 0 && r != 0xa0 && unicode.IsSpace(r) { if current_word.Len() != 0 && r != 0xa0 && unicode.IsSpace(r) {
@ -130,7 +137,7 @@ func format_line_with_indent(output io.Writer, text string, indent string, scree
current_word.WriteRune(r) current_word.WriteRune(r)
} }
} }
if current_word.Len() != 0 { if current_word.Len() != 0 || escapes.Len() != 0 {
print_word(0) print_word(0)
} }
if len(text) > 0 { if len(text) > 0 {
@ -323,6 +330,7 @@ func show_usage(cmd *cobra.Command) error {
fmt.Fprintln(&output, italic_fmt(RootCmd.Name()), opt_fmt(RootCmd.Version), "created by", title_fmt("Kovid Goyal")) fmt.Fprintln(&output, italic_fmt(RootCmd.Name()), opt_fmt(RootCmd.Version), "created by", title_fmt("Kovid Goyal"))
} }
output_text := output.String() output_text := output.String()
// fmt.Printf("%#v\n", output_text)
if stdout_is_terminal && cmd.Annotations["allow-pager"] != "no" { if stdout_is_terminal && cmd.Annotations["allow-pager"] != "no" {
pager := exec.Command(kitty.DefaultPager[0], kitty.DefaultPager[1:]...) pager := exec.Command(kitty.DefaultPager[0], kitty.DefaultPager[1:]...)
pager.Stdin = strings.NewReader(output_text) pager.Stdin = strings.NewReader(output_text)

View File

@ -0,0 +1,18 @@
package cli
import (
"strings"
"testing"
)
func TestFormatLineWithIndent(t *testing.T) {
var output strings.Builder
output.Reset()
indent := " "
format_line_with_indent(&output, "testing \x1b[31mstyled\x1b[m", indent, 11)
expected := indent + "testing \n" + indent + "\x1b[31mstyled\x1b[m\n"
if output.String() != expected {
t.Errorf("%#v != %#v", expected, output.String())
}
}

View File

@ -22,6 +22,16 @@ func EntryPoint(tool_root *cobra.Command) *cobra.Command {
" for permission to perform the specified action, unless the password has been"+ " for permission to perform the specified action, unless the password has been"+
" accepted before or is pre-configured in :file:`kitty.conf`.") " accepted before or is pre-configured in :file:`kitty.conf`.")
root.PersistentFlags().String("password-file", "rc-pass",
"A file from which to read the password. Trailing whitespace is ignored. Relative"+
" paths are resolved from the kitty configuration directory. Use - to read from STDIN."+
" Used if no :option:`--password` is supplied. Defaults to checking for the"+
" :file:`rc-pass` file in the kitty configuration directory.")
root.PersistentFlags().String("password-env", "KITTY_RC_PASSWORD",
"The name of an environment variable to read the password from."+
" Used if no :option:`--password-file` or :option:`--password` is supplied.")
cli.PersistentChoices(root, "use-password", "If no password is available, kitty will usually just send the remote control command without a password. This option can be used to force it to always or never use the supplied password.", "if-available", "always", "never") cli.PersistentChoices(root, "use-password", "If no password is available, kitty will usually just send the remote control command without a password. This option can be used to force it to always or never use the supplied password.", "if-available", "always", "never")
return root return root
} }