mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-13 16:37:27 +00:00
Add unit testing for launcher code
This commit is contained in:
parent
a484d6928e
commit
5c9c8aa424
5 changed files with 189 additions and 30 deletions
|
|
@ -18,6 +18,7 @@
|
|||
static inline void cleanup_decref2(PyObject **p) { Py_CLEAR(*p); }
|
||||
#define RAII_PyObject(name, initializer) __attribute__((cleanup(cleanup_decref2))) PyObject *name = initializer
|
||||
|
||||
#undef MAX
|
||||
#define MAX(x, y) __extension__ ({ \
|
||||
const __typeof__ (x) __a__ = (x); const __typeof__ (y) __b__ = (y); \
|
||||
__a__ > __b__ ? __a__ : __b__;})
|
||||
|
|
@ -105,8 +106,11 @@ alloc_for_cli(CLISpec *spec, size_t sz) {
|
|||
block.used = 0;
|
||||
}
|
||||
char *ans = block.buf + block.used;
|
||||
block.used += sz;
|
||||
ans[sz-1] = 0;
|
||||
block.used += sz;
|
||||
// keep returned memory regions aligned to size of pointer
|
||||
size_t extra = sz % sizeof(void*);
|
||||
if (extra) block.used += sizeof(void*) - extra;
|
||||
return ans;
|
||||
#undef block
|
||||
}
|
||||
|
|
@ -311,15 +315,47 @@ parse_cli_loop(CLISpec *spec, bool save_original_argv, int argc, char **argv) {
|
|||
}
|
||||
|
||||
#ifdef FOR_LAUNCHER
|
||||
static void
|
||||
output_argv(const char *name, int argc, char **argv) {
|
||||
printf("%s:", name);
|
||||
for (int i = 0; i < argc; i++) printf("\x1e%s", argv[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void
|
||||
output_values_for_testing(CLISpec *spec) {
|
||||
value_map_for_loop(&spec->value_map) {
|
||||
printf("%s: ", itr.data->key);
|
||||
CLIValue v = itr.data->val;
|
||||
switch (v.type) {
|
||||
case CLI_VALUE_STRING: case CLI_VALUE_CHOICE:
|
||||
printf("%s", v.strval ? v.strval : ""); break;
|
||||
case CLI_VALUE_BOOL:
|
||||
printf("%d", v.boolval); break;
|
||||
case CLI_VALUE_INT:
|
||||
printf("%lld", v.intval); break;
|
||||
case CLI_VALUE_FLOAT:
|
||||
printf("%f", v.floatval); break;
|
||||
case CLI_VALUE_LIST:
|
||||
break;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
output_for_testing(CLISpec *spec) {
|
||||
output_argv("original_argv", spec->original_argc, spec->original_argv);
|
||||
output_argv("argv", spec->argc, spec->argv);
|
||||
output_values_for_testing(spec);
|
||||
}
|
||||
|
||||
static CLIValue
|
||||
get_cli_val(CLISpec *spec, const char *name) {
|
||||
cli_hash_itr itr = vt_get(&spec->value_map, name);
|
||||
if (vt_is_end(itr)) {
|
||||
flag_hash_itr itr = vt_get(&spec->flag_map, name);
|
||||
if (vt_is_end(itr)) {
|
||||
fprintf(stderr, "Trying to get value for unknown option name: %s\n", name);
|
||||
exit(1);
|
||||
}
|
||||
if (vt_is_end(itr)) return (CLIValue){0};
|
||||
return itr.data->val.defval;
|
||||
}
|
||||
return itr.data->val;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
static void cleanup_free(void *p) { free(*(void**) p); }
|
||||
#define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer
|
||||
|
||||
static bool being_tested = false;
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
static bool
|
||||
|
|
@ -304,12 +305,14 @@ static void
|
|||
exec_kitten(int argc, char *argv[], char *exe_dir) {
|
||||
char exe[PATH_MAX+1] = {0};
|
||||
safe_snprintf(exe, PATH_MAX, "%s/kitten", exe_dir);
|
||||
char **newargv = malloc(sizeof(char*) * (argc + 1));
|
||||
memcpy(newargv, argv, sizeof(char*) * argc);
|
||||
newargv[argc] = 0;
|
||||
newargv[0] = "kitten";
|
||||
argv[0] = "kitten";
|
||||
if (being_tested) {
|
||||
printf("kitten_exe: %s\n", exe);
|
||||
output_argv("argv", argc, argv);
|
||||
exit(0);
|
||||
}
|
||||
errno = 0;
|
||||
execv(exe, newargv);
|
||||
execv(exe, argv);
|
||||
fprintf(stderr, "Failed to execute kitten (%s) with error: %s\n", exe, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -357,18 +360,23 @@ static void
|
|||
handle_fast_commandline(CLISpec *cli_spec, const char *instance_group_prefix, int offset_for_panel_kitten) {
|
||||
CLIOptions opts = {0};
|
||||
RAII_CLISpec(subcommand_cli_spec);
|
||||
#define swap_cli_spec \
|
||||
subcommand_cli_spec.original_argc = cli_spec->original_argc; \
|
||||
subcommand_cli_spec.original_argv = cli_spec->original_argv; \
|
||||
cli_spec = &subcommand_cli_spec;
|
||||
if (offset_for_panel_kitten < 0) {
|
||||
// Look for +open
|
||||
int offset = offset_for_plus_subcommand(cli_spec->original_argc, cli_spec->original_argv, "open");
|
||||
if (offset) {
|
||||
if (!parse_and_check_kitty_cli(&subcommand_cli_spec, cli_spec->original_argc - offset, cli_spec->original_argv + offset)) exit(1);
|
||||
cli_spec = &subcommand_cli_spec;
|
||||
swap_cli_spec;
|
||||
opts.open_url_count = cli_spec->argc;
|
||||
opts.open_urls = cli_spec->argv;
|
||||
}
|
||||
} else if (offset_for_panel_kitten > 0) {
|
||||
parse_and_check_panel_kitten_cli(&subcommand_cli_spec, cli_spec->original_argc - offset_for_panel_kitten, cli_spec->original_argv + offset_for_panel_kitten);
|
||||
cli_spec = &subcommand_cli_spec;
|
||||
parse_and_check_panel_kitten_cli(
|
||||
&subcommand_cli_spec, cli_spec->original_argc - offset_for_panel_kitten, cli_spec->original_argv + offset_for_panel_kitten);
|
||||
swap_cli_spec;
|
||||
}
|
||||
if (get_bool_cli_val(cli_spec, "help")) return;
|
||||
if (get_bool_cli_val(cli_spec, "version")) {
|
||||
|
|
@ -381,21 +389,29 @@ handle_fast_commandline(CLISpec *cli_spec, const char *instance_group_prefix, in
|
|||
}
|
||||
opts.session = get_string_cli_val(cli_spec, "session");
|
||||
if (get_bool_cli_val(cli_spec, "detach")) {
|
||||
#define reopen_or_fail(path, mode, which) { errno = 0; if (freopen(path, mode, which) == NULL) { int s = errno; fprintf(stderr, "Failed to redirect %s to %s with error: ", #which, path); errno = s; perror(NULL); exit(1); } }
|
||||
if (!(opts.session && ((opts.session[0] == '-' && opts.session[1] == 0) || strcmp(opts.session, "/dev/stdin") == 0)))
|
||||
reopen_or_fail("/dev/null", "rb", stdin);
|
||||
const char *detached_log = get_string_cli_val(cli_spec, "detached_log");
|
||||
if (!detached_log || !detached_log[0]) detached_log = "/dev/null";
|
||||
reopen_or_fail(detached_log, "ab", stdout);
|
||||
reopen_or_fail(detached_log, "ab", stderr);
|
||||
if (being_tested) {
|
||||
printf("detach: true\n");
|
||||
printf("detached_log: %s\n", detached_log ? detached_log : "");
|
||||
printf("session: %s\n", opts.session ? opts.session : "");
|
||||
exit(0);
|
||||
} else {
|
||||
#define reopen_or_fail(path, mode, which) { errno = 0; if (freopen(path, mode, which) == NULL) { int s = errno; fprintf(stderr, "Failed to redirect %s to %s with error: ", #which, path); errno = s; perror(NULL); exit(1); } }
|
||||
if (!(opts.session && ((opts.session[0] == '-' && opts.session[1] == 0) || strcmp(opts.session, "/dev/stdin") == 0)))
|
||||
reopen_or_fail("/dev/null", "rb", stdin);
|
||||
if (!detached_log || !detached_log[0]) detached_log = "/dev/null";
|
||||
reopen_or_fail(detached_log, "ab", stdout);
|
||||
reopen_or_fail(detached_log, "ab", stderr);
|
||||
#undef reopen_or_fail
|
||||
if (fork() != 0) exit(0);
|
||||
setsid();
|
||||
if (fork() != 0) exit(0);
|
||||
setsid();
|
||||
}
|
||||
}
|
||||
unsetenv("KITTY_SI_DATA");
|
||||
if (get_bool_cli_val(cli_spec, "single_instance")) {
|
||||
char igbuf[256];
|
||||
opts.wait_for_single_instance_window_close = get_bool_cli_val(cli_spec, "wait_for_single_instance_window_close");
|
||||
opts.instance_group = get_string_cli_val(cli_spec, "instance_group");
|
||||
if (instance_group_prefix && instance_group_prefix[0]) {
|
||||
opts.instance_group = get_string_cli_val(cli_spec, "instance_group");
|
||||
if (opts.instance_group && opts.instance_group[0]) {
|
||||
|
|
@ -405,7 +421,17 @@ handle_fast_commandline(CLISpec *cli_spec, const char *instance_group_prefix, in
|
|||
opts.instance_group = instance_group_prefix;
|
||||
}
|
||||
}
|
||||
single_instance_main(cli_spec->original_argc, cli_spec->original_argv, &opts);
|
||||
if (being_tested) {
|
||||
output_argv("argv", cli_spec->original_argc, cli_spec->original_argv);
|
||||
output_argv("open_urls", opts.open_url_count, opts.open_urls);
|
||||
output_values_for_testing(cli_spec);
|
||||
printf("single_instance: 1\n");
|
||||
printf("instance_group: %s\n", opts.instance_group ? opts.instance_group : "");
|
||||
printf("session: %s\n", opts.session ? opts.session : "");
|
||||
exit(0);
|
||||
} else {
|
||||
single_instance_main(cli_spec->original_argc, cli_spec->original_argv, &opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -417,7 +443,8 @@ delegate_to_kitten_if_possible(int argc, char **argv, char* exe_dir) {
|
|||
const char *kitten = argv[offset + 1];
|
||||
if (is_wrapped_kitten(kitten)) exec_kitten(argc - offset, argv + offset, exe_dir);
|
||||
if (strcmp(kitten, "panel") == 0) {
|
||||
handle_fast_commandline(NULL, "panel", offset + 1);
|
||||
CLISpec t = {.original_argv = argv, .original_argc=argc};
|
||||
handle_fast_commandline(&t, "panel", offset + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -432,8 +459,25 @@ endswith(const char *str, const char *suffix) {
|
|||
return strcmp(str + strLen - suffixLen, suffix) == 0;
|
||||
}
|
||||
|
||||
int main(int argc_, char *argv_[], char* envp[]) {
|
||||
static void
|
||||
output_test_data(RunData *rd) {
|
||||
printf("launched_by_launch_services: %d\n", rd->launched_by_launch_services);
|
||||
printf("is_quick_access_terminal: %d\n", rd->is_quick_access_terminal);
|
||||
char buf[PATH_MAX + 1];
|
||||
if (rd->config_dir == NULL) {
|
||||
if (get_config_dir(buf, sizeof(buf))) rd->config_dir = buf;
|
||||
}
|
||||
printf("config_dir: %s\n", rd->config_dir ? rd->config_dir : "");
|
||||
output_for_testing(rd->cli_spec);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc_, char *argv_[], char* envp[]) {
|
||||
if (argc_ < 1 || !argv_) { fprintf(stderr, "Invalid argc/argv\n"); return 1; }
|
||||
if (argc_ > 1 && strcmp(argv_[1], "+testing-launcher-code") == 0) {
|
||||
being_tested = true;
|
||||
memmove(argv_ + 1, argv_ + 2, (--argc_ - 1) * sizeof(argv_[0]));
|
||||
}
|
||||
if (!ensure_working_stdio()) return 1;
|
||||
char exe[PATH_MAX+1] = {0};
|
||||
if (!read_exe_path(exe, sizeof(exe))) return 1;
|
||||
|
|
@ -483,8 +527,9 @@ int main(int argc_, char *argv_[], char* envp[]) {
|
|||
.exe = exe, .exe_dir = exe_dir, .lib_dir = lib, .cli_spec = &cli_spec, .lc_ctype = lc_ctype,
|
||||
.launched_by_launch_services=launched_by_launch_services, .config_dir = config_dir, .is_quick_access_terminal=is_quick_access_terminal,
|
||||
};
|
||||
ret = run_embedded(&run_data);
|
||||
if (being_tested) output_test_data(&run_data);
|
||||
else ret = run_embedded(&run_data);
|
||||
single_instance_main(-1, NULL, NULL);
|
||||
Py_FinalizeEx();
|
||||
if (!being_tested) Py_FinalizeEx();
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -470,9 +470,10 @@ def kitty_main() -> None:
|
|||
'Run kitty and open the specified files or URLs in it, using launch-actions.conf. For details'
|
||||
' see https://sw.kovidgoyal.net/kitty/open_actions/#scripting-the-opening-of-files-with-kitty-on-macos'
|
||||
'\n\nAll the normal kitty options can be used.')
|
||||
cli_flags = None
|
||||
else:
|
||||
cli_flags = getattr(sys, 'kitty_run_data', {}).get('cli_flags', None)
|
||||
usage = msg = appname = None
|
||||
cli_flags = getattr(sys, 'kitty_run_data', {}).get('cli_flags', None)
|
||||
cli_opts, rest = parse_args(args=args, result_class=CLIOptions, usage=usage, message=msg, appname=appname, preparsed_from_c=cli_flags)
|
||||
if getattr(sys, 'cmdline_args_for_open', False):
|
||||
setattr(sys, 'cmdline_args_for_open', rest)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from kitty.constants import kitty_exe
|
||||
from kitty.fast_data_types import Color, test_cursor_blink_easing_function
|
||||
from kitty.options.utils import DELETE_ENV_VAR, EasingFunction, to_color
|
||||
from kitty.utils import log_error
|
||||
from kitty.utils import log_error, shlex_split
|
||||
|
||||
from . import BaseTest
|
||||
|
||||
|
|
@ -28,13 +30,15 @@ class TestConfParsing(BaseTest):
|
|||
def test_conf_parsing(self):
|
||||
conf_parsing(self)
|
||||
|
||||
def test_launcher(self):
|
||||
launcher(self)
|
||||
|
||||
def test_cli_parsing(self):
|
||||
cli_parsing(self)
|
||||
|
||||
|
||||
def cli_parsing(self):
|
||||
from kitty.cli import CLIOptions, Options, parse_cmdline, parse_option_spec
|
||||
from kitty.utils import shlex_split
|
||||
seq, disabled = parse_option_spec('''\
|
||||
--simple-string -s
|
||||
a simple string
|
||||
|
|
@ -119,6 +123,77 @@ version
|
|||
t('-f=3.142 --int 17', float=3.142, int=17)
|
||||
|
||||
|
||||
def launcher(self):
|
||||
kexe = kitty_exe()
|
||||
def get_report(cmdline: str, launch_services= False):
|
||||
args = list(shlex_split(cmdline))
|
||||
env = dict(os.environ)
|
||||
if launch_services:
|
||||
env['KITTY_LAUNCHED_BY_LAUNCH_SERVICES'] = '1'
|
||||
cp = subprocess.run([kexe, "+testing-launcher-code"] + args, env=env, stdout=subprocess.PIPE)
|
||||
self.assertEqual(cp.returncode, 0)
|
||||
ans = {}
|
||||
for line in cp.stdout.decode().split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
key, val = line.split(':')
|
||||
except ValueError:
|
||||
raise AssertionError(f'Unexpected output from launcher: {line!r}\n{cp.stdout.decode()}')
|
||||
if key in ('argv', 'original_argv', 'open_urls'):
|
||||
val = [x for x in val.split('\x1e') if x]
|
||||
else:
|
||||
val = val.strip()
|
||||
ans[key] = val
|
||||
return ans, cp.stdout.decode().replace('\x1e', ' ')
|
||||
def test(cmdline, assertions):
|
||||
r, output = get_report(cmdline, launch_services=assertions.get('launched_by_launch_services', '0') != '0')
|
||||
for key, expected in assertions.items():
|
||||
self.assertEqual(expected, r.get(key), f'Failed for {key} with command line: {cmdline}\nOutput:\n{output}')
|
||||
return output
|
||||
|
||||
def t(cmdline, **assertions):
|
||||
assertions['is_quick_access_terminal'] = '0'
|
||||
assertions['config_dir'] = os.path.join(os.environ['XDG_CONFIG_HOME'], 'kitty')
|
||||
assertions.setdefault('launched_by_launch_services', '0')
|
||||
test(cmdline, assertions)
|
||||
|
||||
def si(cmdline, **assertions):
|
||||
assertions['single_instance'] = '1'
|
||||
test(cmdline, assertions)
|
||||
|
||||
def dt(cmdline, **assertions):
|
||||
assertions['detach'] = 'true'
|
||||
test(cmdline, assertions)
|
||||
|
||||
def k(cmdline):
|
||||
assertions = {}
|
||||
assertions['argv'] = ['kitten'] + cmdline.split()
|
||||
for prefix in ('+kitten', '+ kitten'):
|
||||
output = test(prefix + ' ' + cmdline, assertions)
|
||||
self.assertIn('kitten_exe:', output)
|
||||
|
||||
def pn(cmdline, **assertions):
|
||||
ig = assertions.get('instance_group')
|
||||
assertions['instance_group'] = f'panel-{ig}' if ig else 'panel'
|
||||
assertions['single_instance'] = '1'
|
||||
assertions['session'] = ''
|
||||
test(cmdline, assertions)
|
||||
|
||||
t('', original_argv=[kexe], argv=[])
|
||||
t('--title=xxx cat', title='xxx', original_argv=[kexe, '--title=xxx', 'cat'], argv=['cat'])
|
||||
k('icat abc xyz')
|
||||
t('+kitten unwrapped xyz', argv=['+kitten', 'unwrapped', 'xyz'])
|
||||
t('+ kitten unwrapped xyz', original_argv=[kexe, '+', 'kitten', 'unwrapped', 'xyz'])
|
||||
si('--single-instance --instance-group=g -T 3', argv=[kexe, '--single-instance', '--instance-group=g', '-T', '3'])
|
||||
t('+open --help', argv=['+open', '--help'])
|
||||
t('+open -1 --help', argv=['+open', '-1', '--help'])
|
||||
si('+open -1 moose', argv=[kexe, '+open', '-1', 'moose'], open_urls=['moose'])
|
||||
si('+open -1 --instance-group=g x y', instance_group='g', open_urls=['x', 'y'])
|
||||
dt('--detach --session=moose --detached-log=xyz', detached_log='xyz', session='moose')
|
||||
pn('+kitten panel -1 --edge=left', edge='left')
|
||||
|
||||
|
||||
def conf_parsing(self):
|
||||
from kitty.config import defaults, load_config
|
||||
from kitty.constants import is_macos
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -1299,8 +1299,10 @@ def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 's
|
|||
werror = '' if args.ignore_compiler_warnings else '-pedantic-errors -Werror'
|
||||
cflags = f'-Wall {werror} -fpie'.split()
|
||||
cppflags = [define(f'WRAPPED_KITTENS=" {wrapped_kittens()} "')]
|
||||
libs: List[str] = []
|
||||
ldflags = shlex.split(os.environ.get('LDFLAGS', ''))
|
||||
xxhash = xxhash_flags()
|
||||
cppflags.extend(xxhash[0])
|
||||
libs: list[str] = xxhash[1]
|
||||
if args.profile or args.sanitize:
|
||||
cflags.append('-g3')
|
||||
if args.sanitize:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue