App-karr
view release on metacpan or search on metacpan
t/81-error-messages.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use File::Temp qw( tempdir );
use Path::Tiny qw( path );
use Carp qw( croak );
use Cwd qw( abs_path getcwd );
use IPC::Open3 qw( open3 );
use Symbol qw( gensym );
use App::karr::Error qw( user_error clean_error );
# Ticket #77: user-facing errors leaked karr's own source locations, because
# Carp appends " at F<Some/Module.pm> line N." even when the message already
# ends in a newline, and because exceptions from underneath karr (Path::Tiny,
# libgit2, captured git stderr) carry the same suffix plus extra lines.
#
# Probed pre-fix, `karr skill install` into an unwritable directory:
#
# mkpath failed for .claude/skills/kanban-issues-karr-cli: Permission denied at
# /.../lib/App/karr/Cmd/Skill.pm line 134.
#
# App::karr::Error is the single place that turns such an error into one clean
# line. This file pins the mechanism and its use in App::karr::Cmd::Skill; the
# rest of the sweep -- App::karr::Role::BoardDiscovery, the two sync failures in
# App::karr::Role::SyncLifecycle, App::karr::Foundation, its Runner, and the
# raw Path::Tiny errors from `karr restore` / `backup` / `init` / `context` --
# is pinned in t/120-error-message-sweep.t. App::karr::Git::_ref_error, which
# used to carry its own inline copy of the same reduction, now calls
# clean_error.
subtest 'croak really does ignore the trailing-newline convention' => sub {
# The premise of the whole ticket. If this ever stops being true, the
# mechanism below is solving a problem that no longer exists.
eval { croak "already newline terminated\n" };
like $@, qr/ at \S+ line \d+/,
'croak appends a call site even to a newline-terminated message';
eval { die "already newline terminated\n" };
is $@, "already newline terminated\n",
'die honours it, which is why user_error uses die';
};
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);
( run in 0.958 second using v1.01-cache-2.11-cpan-4ef0a570458 )