diff --git a/kitty_tests/main.py b/kitty_tests/main.py index b2893e270..a8f4870ca 100644 --- a/kitty_tests/main.py +++ b/kitty_tests/main.py @@ -3,10 +3,12 @@ import importlib import os +import shlex import shutil +import subprocess import sys 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]: @@ -91,24 +93,75 @@ def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None: 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: import argparse parser = argparse.ArgumentParser() parser.add_argument( '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('--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() if args.name and args.name[0] in ('type-check', 'type_check', 'mypy'): type_check() tests = find_all_tests() + go_packages = find_testable_go_packages() + go_filter_spec = '' if 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) + go_pkg_args = [f'kitty/{x}' for x in go_packages] + if 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) run_cli(tests, args.verbosity) + run_go(go_pkg_args, go_filter_spec) diff --git a/test.py b/test.py index 3ee637842..b3dfde59f 100755 --- a/test.py +++ b/test.py @@ -32,6 +32,7 @@ def main() -> None: launcher_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty', 'launcher') path = f'{launcher_dir}{os.pathsep}{path}' PYTHON_FOR_TYPE_CHECK = shutil.which('python') or shutil.which('python3') or '' + gohome = os.path.expanduser('~/go') with TemporaryDirectory() as tdir, env_vars( PYTHONWARNINGS='error', HOME=tdir, USERPROFILE=tdir, PATH=path, XDG_CONFIG_HOME=os.path.join(tdir, '.config'), @@ -40,6 +41,8 @@ def main() -> None: XDG_CACHE_HOME=os.path.join(tdir, '.cache'), 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') getattr(m, 'run_tests')() diff --git a/tools/cli/infrastructure.go b/tools/cli/infrastructure.go index 658735216..6eb74cd92 100644 --- a/tools/cli/infrastructure.go +++ b/tools/cli/infrastructure.go @@ -83,6 +83,7 @@ func format_line_with_indent(output io.Writer, text string, indent string, scree fmt.Fprint(output, indent) in_escape := 0 var current_word strings.Builder + var escapes strings.Builder print_word := func(r rune) { 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.WriteString(s) } - fmt.Fprint(output, current_word.String()) - current_word.Reset() + 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() + } if r > 0 { 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) { in_escape = 0 } - fmt.Fprint(output, string(r)) + escapes.WriteRune(r) continue } 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 { print_word(0) } - fmt.Fprint(output, string(r)) + escapes.WriteRune(r) continue } 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) } } - if current_word.Len() != 0 { + if current_word.Len() != 0 || escapes.Len() != 0 { print_word(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")) } output_text := output.String() + // fmt.Printf("%#v\n", output_text) if stdout_is_terminal && cmd.Annotations["allow-pager"] != "no" { pager := exec.Command(kitty.DefaultPager[0], kitty.DefaultPager[1:]...) pager.Stdin = strings.NewReader(output_text) diff --git a/tools/cli/infrastructure_test.go b/tools/cli/infrastructure_test.go new file mode 100644 index 000000000..a905a2fe9 --- /dev/null +++ b/tools/cli/infrastructure_test.go @@ -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()) + } +} diff --git a/tools/cmd/at/main.go b/tools/cmd/at/main.go index ae7027ad5..5153f27d2 100644 --- a/tools/cmd/at/main.go +++ b/tools/cmd/at/main.go @@ -22,6 +22,16 @@ func EntryPoint(tool_root *cobra.Command) *cobra.Command { " for permission to perform the specified action, unless the password has been"+ " 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") return root }