Langertha
view release on metacpan or search on metacpan
t/96_raid_orchestration.t view on Meta::CPAN
is($result->text, 'stage2:yes', 'second step reads state written by first step');
};
subtest 'Parallel context isolation and merge behavior' => sub {
my $parallel = Langertha::Raid::Parallel->new(steps => [
Test::Runnable::Step->new(
name => 'left',
result_cb => sub {
my ( $ctx ) = @_;
$ctx->state->{counter}++;
$ctx->artifacts->{branch} = 'left';
return Langertha::Result->final('L');
},
),
Test::Runnable::Step->new(
name => 'right',
result_cb => sub {
my ( $ctx ) = @_;
$ctx->state->{counter}++;
$ctx->artifacts->{branch} = 'right';
return Langertha::Result->final('R');
},
),
]);
my $ctx = Langertha::RunContext->new(
input => 'seed',
state => { counter => 0 },
);
my $result = $parallel->run_f($ctx)->get;
ok($result->is_final, 'parallel returns final when all branches are final');
is($ctx->state->{counter}, 0, 'parent state was not mutated by branch internals');
is($result->text, "L\nR", 'branch final texts are aggregated in stable order');
is(scalar keys %{$ctx->artifacts->{parallel_branches}}, 2, 'both branch contexts merged');
};
subtest 'Parallel propagation and error path' => sub {
my $aborting = Langertha::Raid::Parallel->new(steps => [
Test::Runnable::Step->new(name => 'ok', type => 'final', text => 'ok'),
Test::Runnable::Step->new(name => 'bad', type => 'abort', content => 'stop'),
]);
my $abort_result = $aborting->run_f(Langertha::RunContext->new(input => 'x'))->get;
ok($abort_result->is_abort, 'abort from a branch aborts the whole parallel raid');
my $questioning = Langertha::Raid::Parallel->new(steps => [
Test::Runnable::Step->new(name => 'q', type => 'question', content => 'which one?'),
Test::Runnable::Step->new(name => 'ok', type => 'final', text => 'ok'),
]);
my $question_result = $questioning->run_f(Langertha::RunContext->new(input => 'x'))->get;
ok($question_result->is_question, 'question propagates when no abort exists');
my $dying = Langertha::Raid::Parallel->new(steps => [
Test::Runnable::Step->new(name => 'explode', die_message => 'parallel boom'),
]);
my $dying_result = $dying->run_f(Langertha::RunContext->new(input => 'x'))->get;
ok($dying_result->is_abort, 'branch exception becomes abort');
like($dying_result->content, qr/parallel boom/, 'abort keeps branch failure reason');
};
subtest 'Loop max iterations and stop callback' => sub {
my $step = Test::Runnable::Step->new(
name => 'counter',
result_cb => sub {
my ( $ctx ) = @_;
$ctx->state->{n} = ($ctx->state->{n} // 0) + 1;
return Langertha::Result->final('n=' . $ctx->state->{n});
},
);
my $loop = Langertha::Raid::Loop->new(
steps => [$step],
max_loops => 3,
);
my $ctx = Langertha::RunContext->new(input => 'start');
my $result = $loop->run_f($ctx)->get;
ok($result->is_final, 'loop finalizes after max_loops');
is($result->text, 'n=3', 'loop ran exactly three iterations');
is($ctx->metadata->{loop_iterations}, 3, 'loop stores total iterations');
my $loop_cb = Langertha::Raid::Loop->new(
steps => [$step],
max_loops => 10,
continue_while => sub {
my ( $ctx, $iteration ) = @_;
return $iteration < 2;
},
);
my $ctx2 = Langertha::RunContext->new(input => 'start');
my $result2 = $loop_cb->run_f($ctx2)->get;
is($result2->text, 'n=2', 'continue_while can stop before max_loops');
is($ctx2->metadata->{loop_iterations}, 2, 'iteration metadata reflects early stop');
};
subtest 'Loop propagation and error path' => sub {
my $loop_pause = Langertha::Raid::Loop->new(
steps => [Test::Runnable::Step->new(name => 'pause', type => 'pause', content => 'wait')],
max_loops => 5,
);
my $pause_result = $loop_pause->run_f(Langertha::RunContext->new(input => 'x'))->get;
ok($pause_result->is_pause, 'pause propagates out of loop');
my $loop_error = Langertha::Raid::Loop->new(
steps => [Test::Runnable::Step->new(name => 'boom', die_message => 'loop failed')],
max_loops => 2,
);
my $error_result = $loop_error->run_f(Langertha::RunContext->new(input => 'x'))->get;
ok($error_result->is_abort, 'loop step exception returns abort');
like($error_result->content, qr/loop failed/, 'loop abort includes exception');
};
subtest 'Nested raid compositions are supported' => sub {
my $raider = Test::CompatRaider->new(engine => Test::DummyEngine->new, prefix => 'R:');
my $parallel = Langertha::Raid::Parallel->new(steps => [
Test::Runnable::Step->new(name => 'p1', type => 'final', text => 'P1'),
$raider,
]);
my $loop_with_seq = Langertha::Raid::Loop->new(
max_loops => 2,
steps => [
Langertha::Raid::Sequential->new(steps => [
Test::Runnable::Step->new(
name => 'dot',
result_cb => sub {
my ( $ctx ) = @_;
return Langertha::Result->final(($ctx->input // '') . '.');
},
),
]),
],
);
my $top = Langertha::Raid::Sequential->new(steps => [
Test::Runnable::Step->new(name => 'start', type => 'final', text => 'S'),
$parallel,
$loop_with_seq,
]);
my $result = $top->run_f(Langertha::RunContext->new(input => 'seed'))->get;
ok($result->is_final, 'nested orchestration returns final');
like($result->text, qr/\.\.$/, 'loop-with-sequential runs within nested tree');
};
done_testing;
( run in 1.338 second using v1.01-cache-2.11-cpan-71847e10f99 )