App-karr
view release on metacpan or search on metacpan
t/81-error-messages.t view on Meta::CPAN
subtest 'user_error raises exactly the message it was given' => sub {
eval { user_error('Task 7 not found') };
is $@, "Task 7 not found\n", 'one line, newline terminated, nothing appended';
eval { user_error( 'Could not write ', '/some/path', ': ', 'Permission denied' ) };
is $@, "Could not write /some/path: Permission denied\n", 'parts are concatenated';
eval { user_error("trailing whitespace and newlines \n\n") };
is $@, "trailing whitespace and newlines\n", 'trailing whitespace collapses to one newline';
eval { user_error( 'defined', undef, ' parts only' ) };
is $@, "defined parts only\n", 'undef parts are dropped';
eval { user_error('anything') };
unlike $@, qr/ at \S+ line \d+/, 'no file or line number anywhere in it';
unlike $@, qr/\.pm/, 'no module path either';
};
subtest 'clean_error reduces an internal error to one line' => sub {
is clean_error("boom at /some/where/Module.pm line 42.\n"), 'boom',
'a die string loses its call site';
is clean_error("boom at /some/where/Module.pm line 42.\n\t...propagated at x line 9.\n"),
'boom', 'and everything Carp propagated after it';
is clean_error("first line of git noise\nsecond line\nthird line\n"),
'first line of git noise', 'a multi-line backend error keeps only its first line';
is clean_error("no call site here"), 'no call site here', 'a clean message passes through';
is clean_error(''), 'unknown error', 'an empty error still says something';
is clean_error(" \n "), 'unknown error', 'so does a whitespace-only one';
};
{
# Stand-in for a libgit2 exception, which carries its text in ->message.
package KarrTestErrorObject;
sub message { return "libgit2 style message at /somewhere/Git.pm line 7.\n" }
}
subtest 'clean_error handles the exception objects karr actually meets' => sub {
my $pt = eval { path('/nonexistent-karr-test-dir/nope')->slurp_utf8; 1 } ? undef : $@;
isa_ok $pt, 'Path::Tiny::Error', 'Path::Tiny raised an object';
like "$pt", qr/ at \S+ line \d+/, 'which stringifies with a call site';
my $clean = clean_error($pt);
unlike $clean, qr/ at \S+ line \d+/, 'clean_error strips it';
unlike $clean, qr/\n/, 'and leaves a single line';
like $clean, qr/No such file or directory/, 'while keeping the reason';
# libgit2 raises objects that carry their text in ->message.
my $obj = bless {}, 'KarrTestErrorObject';
is clean_error($obj), 'libgit2 style message', 'an object with ->message is read through it';
};
subtest 'karr skill install reports an unwritable target without a source location' => sub {
plan skip_all => 'running as root: an unwritable directory is still writable'
if $> == 0;
my $ROOT = abs_path('.');
my $home = tempdir( CLEANUP => 1 );
path($home)->child('.claude/skills')->mkpath;
chmod 0500, path($home)->child('.claude/skills')->stringify
or plan skip_all => "cannot chmod the target directory: $!";
my $old = getcwd();
chdir $home or die "chdir $home: $!";
my $errfh = gensym;
my $pid = open3( undef, my $outfh, $errfh,
$^X, "-I$ROOT/lib", "$ROOT/bin/karr", 'skill', 'install', '--agent', 'claude-code' );
my $out = do { local $/; <$outfh> };
my $err = do { local $/; <$errfh> };
waitpid( $pid, 0 );
my $exit = $? >> 8;
chdir $old or die "chdir $old: $!";
chmod 0700, path($home)->child('.claude/skills')->stringify;
isnt $exit, 0, 'the failed install is reported as a failure';
like $err, qr/Could not write /, 'stderr says what karr could not do';
like $err, qr/Permission denied/, 'and keeps the reason from the OS';
unlike $err, qr/ at \S+ line \d+/, 'no "at FILE line N." suffix'
or diag "stderr was:\n$err";
unlike $err, qr/Skill\.pm/, 'no karr module path leaks'
or diag "stderr was:\n$err";
my @lines = split /\n/, $err;
is scalar(@lines), 1, 'exactly one line of error'
or diag "stderr was:\n$err";
};
done_testing;
( run in 0.733 second using v1.01-cache-2.11-cpan-6736b670a1e )