├── t ├── yath_script │ ├── nested │ │ ├── scripts │ │ │ └── yath │ │ └── .yath.user.rc │ └── .yath.rc ├── integration │ ├── includes │ │ ├── .yath.rc │ │ ├── not-perl.sh │ │ ├── default.tx │ │ ├── default-i.tx │ │ ├── dot-last.tx │ │ ├── order-ibili.tx │ │ ├── order-ilibi.tx │ │ └── not-perl.pl │ ├── reload │ │ └── lib │ │ │ ├── Preload │ │ │ ├── nonperl1 │ │ │ ├── nonperl2 │ │ │ ├── IncChange.pm │ │ │ ├── A.pm │ │ │ ├── WarningA.pm │ │ │ ├── WarningB.pm │ │ │ ├── B.pm │ │ │ ├── ExporterA.pm │ │ │ ├── ExceptionA.pm │ │ │ ├── ExporterB.pm │ │ │ ├── ExceptionB.pm │ │ │ └── Churn.pm │ │ │ └── Preload.pm │ ├── retry-symlinks │ │ ├── symlink.tl │ │ └── retry.tx │ ├── test-broken-symlinks │ │ ├── keepme │ │ └── pass.tx │ ├── preload │ │ ├── lib │ │ │ ├── AAA.pm │ │ │ ├── BBB.pm │ │ │ ├── CCC.pm │ │ │ ├── FAST.pm │ │ │ ├── Broken.pm │ │ │ ├── TestSimplePreload.pm │ │ │ ├── TestBadPreload.pm │ │ │ └── TestPreload.pm │ │ ├── preload_test.tx │ │ ├── simple_test.tx │ │ ├── retry.tx │ │ ├── aaa.tx │ │ ├── bbb.tx │ │ ├── fast.tx │ │ ├── no_preload.tx │ │ ├── slow.tx │ │ └── ccc.tx │ ├── test-symlinks │ │ ├── symlink_to_base.xt │ │ └── _base.xt │ ├── projects │ │ ├── bar │ │ │ ├── lib │ │ │ │ ├── Bar.pm │ │ │ │ ├── Baz.pm │ │ │ │ └── Foo.pm │ │ │ └── t │ │ │ │ └── pass.tx │ │ ├── baz │ │ │ ├── lib │ │ │ │ ├── Baz.pm │ │ │ │ ├── Bar.pm │ │ │ │ └── Foo.pm │ │ │ └── t │ │ │ │ ├── fail.txx │ │ │ │ └── pass.tx │ │ └── foo │ │ │ ├── lib │ │ │ ├── Foo.pm │ │ │ ├── Bar.pm │ │ │ └── Baz.pm │ │ │ └── t │ │ │ └── pass.tx │ ├── smoke │ │ ├── a.tx │ │ ├── b.tx │ │ ├── c.tx │ │ ├── d.tx │ │ ├── e.tx │ │ ├── f.tx │ │ ├── g.tx │ │ ├── h.tx │ │ └── lib │ │ │ └── SmokePlugin.pm │ ├── test │ │ ├── fail.txx │ │ ├── pass.tx │ │ └── pass.txxx │ ├── times │ │ ├── pass.tx │ │ └── pass2.tx │ ├── failed │ │ ├── fail.tx │ │ └── pass.tx │ ├── log_dir │ │ └── foo.tx │ ├── persist │ │ ├── fail.txx │ │ └── pass.tx │ ├── replay │ │ ├── fail.tx │ │ └── pass.tx │ ├── speedtag │ │ ├── pass.tx │ │ └── pass2.tx │ ├── stamps │ │ ├── fail.tx │ │ ├── pass.tx │ │ └── lib │ │ │ └── App │ │ │ └── Yath │ │ │ └── Plugin │ │ │ └── TestPlugin.pm │ ├── test-durations │ │ ├── fast-01.tx │ │ ├── fast-02.tx │ │ ├── fast-03.tx │ │ ├── fast-04.tx │ │ ├── slow-01.tx │ │ └── slow-02.tx │ ├── test-w │ │ ├── b.tx │ │ └── a.tx │ ├── coverage │ │ ├── lib │ │ │ ├── Bx.pm │ │ │ ├── Cx.pm │ │ │ ├── Ax.pm │ │ │ ├── Manager.pm │ │ │ └── Plugin.pm │ │ ├── once.tx │ │ ├── x.tx │ │ ├── b.tx │ │ ├── open.tx │ │ ├── c.tx │ │ └── a.tx │ ├── concurrency │ │ ├── a.tx │ │ ├── b.tx │ │ ├── c.tx │ │ ├── d.tx │ │ └── e.tx │ ├── plugin │ │ ├── a.tx │ │ ├── b.tx │ │ ├── c.tx │ │ ├── d.tx │ │ └── test.tx │ ├── failure_cases │ │ ├── noplan.tx │ │ ├── notok.tx │ │ ├── timeout.tx │ │ ├── badplan.tx │ │ ├── post_exit_timeout.tx │ │ ├── subtest.tx │ │ ├── exit.tx │ │ ├── missingnums.tx │ │ ├── parse_error.tx │ │ ├── dupnums.tx │ │ ├── buffered_subtest_abrupt_end.tx │ │ ├── nested_subtest.tx │ │ ├── buffered_subtest_abrupt_end_nested.tx │ │ └── nested_subtest_exception.tx │ ├── reload_syntax_error.tx │ ├── verbose_env │ │ ├── not_verbose.tx │ │ ├── verbose1.tx │ │ └── verbose2.tx │ ├── resource │ │ ├── a.tx │ │ ├── b.tx │ │ ├── c.tx │ │ ├── d.tx │ │ └── Resource.pm │ ├── tapsubtest │ │ └── test.tx │ ├── signals │ │ └── abrt_or_iot.t │ ├── test-durations.json │ ├── slots_per_job3.t │ ├── slots_per_job.t │ ├── slots_per_job2.t │ ├── retry-timeout │ │ └── retry.tx │ ├── test-inc │ │ └── check-INC.tx │ ├── test-w.t │ ├── signals.t │ ├── encoding │ │ ├── no-plugin.tx │ │ └── plugin.tx │ ├── tapsubtest.t │ ├── retry │ │ └── retry.tx │ ├── stamps.t │ ├── log_dir.t │ ├── init.t │ ├── verbose_env.t │ ├── times.t │ ├── includes.t │ ├── failed.t │ ├── failure_cases.t │ ├── speedtag.t │ ├── replay.t │ ├── encoding.t │ ├── reload_syntax_error.t │ ├── smoke.t │ ├── help.t │ ├── projects.t │ ├── persist.t │ ├── resource.t │ └── plugin.t ├── lib │ └── App │ │ └── Yath │ │ ├── Command │ │ ├── broken.pm │ │ └── fake.pm │ │ └── Plugin │ │ ├── Options.pm │ │ └── Fail.pm ├── child_exit_282.t ├── unit │ ├── Test2 │ │ ├── Harness │ │ │ ├── Util │ │ │ │ ├── Term.t │ │ │ │ ├── File │ │ │ │ │ ├── JSONL.t │ │ │ │ │ ├── Value.t │ │ │ │ │ ├── JSON.t │ │ │ │ │ └── Stream.t │ │ │ │ ├── JSON.t │ │ │ │ └── File.t │ │ │ ├── Runner │ │ │ │ ├── Resource │ │ │ │ │ └── SharedJobSlots │ │ │ │ │ │ └── .sharedjobslots.yml │ │ │ │ └── DepTracer.t │ │ │ ├── Settings │ │ │ │ └── Prefix.t │ │ │ ├── Settings.t │ │ │ └── Util.t │ │ └── Tools │ │ │ └── HarnessTester.t │ └── App │ │ └── Yath │ │ ├── Plugin.t │ │ └── Command │ │ └── init.t ├── 0-load_all.t └── 1-pod_name.t ├── t2 ├── dollardot.t ├── builder.t ├── findbin.t ├── non_perl │ ├── test.sh │ └── test.c ├── simple.t ├── no_stdout_eol.t ├── require_file.pm ├── data.t ├── magic_vars.t ├── exception.t ├── subtests_buffered.t ├── subtests_streamed.t ├── require_file.t ├── vars.t ├── ending.t ├── ipc_reexec.t ├── caller.t ├── relative_paths.t ├── output.t ├── relative_paths_no_fork.t ├── subtests.t ├── utf8.t ├── lib │ └── App │ │ └── Yath │ │ └── Plugin │ │ └── SelfTest.pm ├── tmp_perms.t └── utf8-2.t ├── .vimrc ├── .yath.rc ├── cpanfile.ci ├── .travis.yml ├── .gitignore ├── lib ├── App │ └── Yath │ │ ├── Command │ │ ├── projects.pm │ │ ├── do.pm │ │ ├── which.pm │ │ ├── kill.pm │ │ ├── stop.pm │ │ ├── reload.pm │ │ ├── auditor.pm │ │ ├── abort.pm │ │ ├── init.pm │ │ ├── collector.pm │ │ ├── ps.pm │ │ ├── help.pm │ │ └── watch.pm │ │ ├── Options │ │ ├── Persist.pm │ │ ├── Collector.pm │ │ └── Workspace.pm │ │ ├── Converting.pm │ │ └── Plugin │ │ └── SysInfo.pm └── Test2 │ ├── Harness.pm │ ├── Harness │ ├── Runner │ │ ├── Preloader │ │ │ └── Stage.pm │ │ ├── Constants.pm │ │ ├── Spawn.pm │ │ └── Run.pm │ ├── Util │ │ ├── UUID.pm │ │ ├── File │ │ │ ├── JSONL.pm │ │ │ ├── JSON.pm │ │ │ └── Value.pm │ │ └── Term.pm │ └── IPC │ │ └── Process.pm │ └── Formatter │ └── QVF.pm ├── appveyor.yml ├── test.pl ├── .perltidyrc ├── release-scripts ├── generate_command_pod.pl └── generate_options_pod.pl ├── .github └── workflows │ └── testsuite.yml ├── xt └── author │ └── pod-spell.t └── cpanfile /t/yath_script/nested/scripts/yath: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/integration/includes/.yath.rc: -------------------------------------------------------------------------------- 1 | [test] 2 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/nonperl1: -------------------------------------------------------------------------------- 1 | 1; 2 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/nonperl2: -------------------------------------------------------------------------------- 1 | 2; 2 | -------------------------------------------------------------------------------- /t/integration/retry-symlinks/symlink.tl: -------------------------------------------------------------------------------- 1 | retry.tx -------------------------------------------------------------------------------- /t/integration/test-broken-symlinks/keepme: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /t/integration/preload/lib/AAA.pm: -------------------------------------------------------------------------------- 1 | package AAA; 2 | 1; 3 | -------------------------------------------------------------------------------- /t/integration/preload/lib/BBB.pm: -------------------------------------------------------------------------------- 1 | package BBB; 2 | 1; 3 | -------------------------------------------------------------------------------- /t/integration/preload/lib/CCC.pm: -------------------------------------------------------------------------------- 1 | package CCC; 2 | 1; 3 | -------------------------------------------------------------------------------- /t/integration/preload/lib/FAST.pm: -------------------------------------------------------------------------------- 1 | package FAST; 2 | 1; 3 | -------------------------------------------------------------------------------- /t/integration/test-symlinks/symlink_to_base.xt: -------------------------------------------------------------------------------- 1 | _base.xt -------------------------------------------------------------------------------- /t/integration/projects/bar/lib/Bar.pm: -------------------------------------------------------------------------------- 1 | package Bar; 2 | 3 | 1; 4 | -------------------------------------------------------------------------------- /t/integration/projects/baz/lib/Baz.pm: -------------------------------------------------------------------------------- 1 | package Baz; 2 | 3 | 1; 4 | -------------------------------------------------------------------------------- /t/integration/projects/foo/lib/Foo.pm: -------------------------------------------------------------------------------- 1 | package Foo; 2 | 3 | 1; 4 | -------------------------------------------------------------------------------- /t/integration/preload/lib/Broken.pm: -------------------------------------------------------------------------------- 1 | package Broken; 2 | 3 | die "This is broken"; 4 | -------------------------------------------------------------------------------- /t/integration/projects/bar/lib/Baz.pm: -------------------------------------------------------------------------------- 1 | die "Loaded Baz.pm from the wrong project!"; 2 | -------------------------------------------------------------------------------- /t/integration/projects/bar/lib/Foo.pm: -------------------------------------------------------------------------------- 1 | die "Loaded Foo.pm from the wrong project!"; 2 | -------------------------------------------------------------------------------- /t/integration/projects/baz/lib/Bar.pm: -------------------------------------------------------------------------------- 1 | die "Loaded Bar.pm from the wrong project!"; 2 | -------------------------------------------------------------------------------- /t/integration/projects/baz/lib/Foo.pm: -------------------------------------------------------------------------------- 1 | die "Loaded Foo.pm from the wrong project!"; 2 | -------------------------------------------------------------------------------- /t/integration/projects/foo/lib/Bar.pm: -------------------------------------------------------------------------------- 1 | die "Loaded Bar.pm from the wrong project!"; 2 | -------------------------------------------------------------------------------- /t/integration/projects/foo/lib/Baz.pm: -------------------------------------------------------------------------------- 1 | die "Loaded Baz.pm from the wrong project!"; 2 | -------------------------------------------------------------------------------- /t/integration/includes/not-perl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | exec $YATH_PERL not-perl.pl 3 | -------------------------------------------------------------------------------- /t/integration/preload/lib/TestSimplePreload.pm: -------------------------------------------------------------------------------- 1 | package TestSimplePreload; 2 | 3 | 1; 4 | -------------------------------------------------------------------------------- /t/integration/smoke/a.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test/fail.txx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(0, "Fail"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/times/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t2/dollardot.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | is($., undef, "\$. is set to undef"); 3 | done_testing; 4 | -------------------------------------------------------------------------------- /t/integration/failed/fail.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(0, "Fail"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/failed/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/log_dir/foo.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/persist/fail.txx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(0, "Fail"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/persist/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/replay/fail.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(0, "Fail"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/replay/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/smoke/b.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | 7 | -------------------------------------------------------------------------------- /t/integration/smoke/c.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | 7 | -------------------------------------------------------------------------------- /t/integration/smoke/d.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | 7 | -------------------------------------------------------------------------------- /t/integration/smoke/e.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | 7 | -------------------------------------------------------------------------------- /t/integration/smoke/f.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | 7 | -------------------------------------------------------------------------------- /t/integration/smoke/g.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | 7 | -------------------------------------------------------------------------------- /t/integration/smoke/h.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "pass"); 4 | 5 | done_testing; 6 | 7 | -------------------------------------------------------------------------------- /t/integration/speedtag/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/speedtag/pass2.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/stamps/fail.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(0, "Fail"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/stamps/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/times/pass2.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/projects/baz/t/fail.txx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(0, "Fail"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-durations/fast-01.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok 1, "$0"; 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-durations/fast-02.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok 1, "$0"; 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-durations/fast-03.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok 1, "$0"; 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-durations/fast-04.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok 1, "$0"; 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-durations/slow-01.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok 1, "$0"; 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-durations/slow-02.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok 1, "$0"; 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-broken-symlinks/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(1, "Pass"); 4 | 5 | done_testing; 6 | -------------------------------------------------------------------------------- /t/integration/test-w/b.tx: -------------------------------------------------------------------------------- 1 | #!perl 2 | use Test2::V0; 3 | ok(!$^W,'-w should not leak'); 4 | done_testing; 5 | -------------------------------------------------------------------------------- /t2/builder.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | # HARNESS-DURATION-SHORT 3 | 4 | ok(1, "pass"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/test-w/a.tx: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | use Test2::V0; 3 | ok($^W,'-w should be honoured'); 4 | done_testing; 5 | -------------------------------------------------------------------------------- /t/lib/App/Yath/Command/broken.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::Broken; 2 | 3 | die "This command is broken!"; 4 | -------------------------------------------------------------------------------- /t2/findbin.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok 'FindBin'; 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/coverage/lib/Bx.pm: -------------------------------------------------------------------------------- 1 | package Bx; 2 | use strict; 3 | use warnings; 4 | 5 | sub b { 'b' } 6 | 7 | 1; 8 | -------------------------------------------------------------------------------- /t/integration/coverage/lib/Cx.pm: -------------------------------------------------------------------------------- 1 | package Cx; 2 | use strict; 3 | use warnings; 4 | 5 | sub c { 'c' } 6 | 7 | 1; 8 | -------------------------------------------------------------------------------- /t2/non_perl/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # HARNESS-DURATION-SHORT 3 | echo "ok 1"; 4 | echo "1..1"; 5 | exit 0; 6 | -------------------------------------------------------------------------------- /t/integration/concurrency/a.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | sleep 1; 4 | ok(1, "pass"); 5 | sleep 1; 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/concurrency/b.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | sleep 1; 4 | ok(1, "pass"); 5 | sleep 1; 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/concurrency/c.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | sleep 1; 4 | ok(1, "pass"); 5 | sleep 1; 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/concurrency/d.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | sleep 1; 4 | ok(1, "pass"); 5 | sleep 1; 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/concurrency/e.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | sleep 1; 4 | ok(1, "pass"); 5 | sleep 1; 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/plugin/a.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | ok(1); 3 | is($ENV{T2_HARNESS_JOB_DURATION}, 'short'); 4 | done_testing; 5 | -------------------------------------------------------------------------------- /t/integration/plugin/b.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | ok(1); 3 | is($ENV{T2_HARNESS_JOB_DURATION}, 'medium'); 4 | done_testing; 5 | -------------------------------------------------------------------------------- /t/integration/plugin/c.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | ok(1); 3 | is($ENV{T2_HARNESS_JOB_DURATION}, 'medium'); 4 | done_testing; 5 | -------------------------------------------------------------------------------- /t2/simple.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # HARNESS-DURATION-SHORT 3 | 4 | use Test2::V0; 5 | ok(1, "pass"); 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/plugin/d.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | ok(1); 3 | 4 | is($ENV{T2_HARNESS_JOB_DURATION}, 'medium'); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t2/no_stdout_eol.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | # HARNESS-DURATION-SHORT 5 | 6 | print "1..2\nok\nok"; 7 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | :map :w:! perl -Ilib scripts/yath -A -T -v % 2 | :imap :w:! perl -Ilib scripts/yath -A -T -v % 3 | -------------------------------------------------------------------------------- /t/integration/failure_cases/noplan.tx: -------------------------------------------------------------------------------- 1 | print < 2 | int main() { 3 | printf("ok 1 - assertion\n"); 4 | printf("1..1\n"); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /t2/require_file.pm: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | sub file_loaded { 1 } 5 | 6 | is(__PACKAGE__, 'main', "in main package"); 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /cpanfile.ci: -------------------------------------------------------------------------------- 1 | # modules required for Continuous Integrations 2 | # outside of Dist::Zilla vision 3 | 4 | requires "Devel::AssertOS" => "0"; 5 | 6 | -------------------------------------------------------------------------------- /t/integration/failure_cases/badplan.tx: -------------------------------------------------------------------------------- 1 | print < sub { 4 | ok($ENV{FAILURE_DO_PASS}, "check env"); 5 | }; 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/failure_cases/exit.tx: -------------------------------------------------------------------------------- 1 | use Test2::Tools::Tiny; 2 | use strict; 3 | use warnings; 4 | 5 | ok(1); 6 | done_testing; 7 | 8 | exit(123) unless $ENV{FAILURE_DO_PASS}; 9 | -------------------------------------------------------------------------------- /t/integration/preload/aaa.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | is($ENV{T2_HARNESS_STAGE}, 'AAA', "Running in stage 'AAA'"); 4 | ok($INC{'AAA.pm'}, "Preloaded AAA"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/preload/bbb.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | is($ENV{T2_HARNESS_STAGE}, 'BBB', "Running in stage 'BBB'"); 4 | ok($INC{'BBB.pm'}, "Preloaded BBB"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/failure_cases/missingnums.tx: -------------------------------------------------------------------------------- 1 | my $i = 1; 2 | 3 | print "ok " . $i++ . "\n"; 4 | $i++ unless $ENV{FAILURE_DO_PASS}; 5 | print "ok " . $i++ . "\n"; 6 | 7 | print "1..2\n"; 8 | -------------------------------------------------------------------------------- /t/integration/preload/fast.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok($INC{'FAST.pm'}, "Preloaded fast"); 4 | is($ENV{T2_HARNESS_STAGE}, 'FAST', "Running in 'FAST' stage"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/preload/no_preload.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-NO-PRELOAD 3 | 4 | is($ENV{T2_HARNESS_STAGE}, 'NOPRELOAD', "Running in 'NOPRELOAD' stage"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/verbose_env/not_verbose.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | is($ENV{T2_HARNESS_IS_VERBOSE}, 0, "Not verbose"); 4 | is($ENV{HARNESS_IS_VERBOSE}, 0, "Not verbose"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/verbose_env/verbose1.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | is($ENV{T2_HARNESS_IS_VERBOSE}, 1, "Verbosity level 1"); 4 | is($ENV{HARNESS_IS_VERBOSE}, 1, "Verbosity level 1"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/verbose_env/verbose2.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | is($ENV{T2_HARNESS_IS_VERBOSE}, 2, "Verbosity level 2"); 4 | is($ENV{HARNESS_IS_VERBOSE}, 2, "Verbosity level 2"); 5 | 6 | done_testing; 7 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/IncChange.pm: -------------------------------------------------------------------------------- 1 | package Preload::IncChange; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 7 | } 8 | 9 | 1; 10 | -------------------------------------------------------------------------------- /t/integration/coverage/lib/Ax.pm: -------------------------------------------------------------------------------- 1 | package Ax; 2 | use strict; 3 | use warnings; 4 | 5 | # This is here for simulating a non-sub change 6 | my $A = 'a'; 7 | 8 | sub a { 'a' } 9 | sub aa { 'aa' } 10 | 11 | 1; 12 | -------------------------------------------------------------------------------- /t/integration/plugin/test.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(!$INC{'App/Yath/Plugin/TestPlugin.pm'}, "Plugin is not loaded for test processes"); 4 | 5 | is($ENV{T2_HARNESS_JOB_DURATION}, 'long'); 6 | 7 | done_testing(); 8 | -------------------------------------------------------------------------------- /t/integration/resource/a.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok($ENV{RESOURCE_TEST}, "Set the env var"); 4 | is($ARGV[0], $ENV{RESOURCE_TEST}, "Set the test cli argument to the same value"); 5 | 6 | sleep 1; 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/integration/resource/b.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok($ENV{RESOURCE_TEST}, "Set the env var"); 4 | is($ARGV[0], $ENV{RESOURCE_TEST}, "Set the test cli argument to the same value"); 5 | 6 | sleep 1; 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/integration/resource/c.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok($ENV{RESOURCE_TEST}, "Set the env var"); 4 | is($ARGV[0], $ENV{RESOURCE_TEST}, "Set the test cli argument to the same value"); 5 | 6 | sleep 1; 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/integration/resource/d.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok($ENV{RESOURCE_TEST}, "Set the env var"); 4 | is($ARGV[0], $ENV{RESOURCE_TEST}, "Set the test cli argument to the same value"); 5 | 6 | sleep 1; 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/integration/test-symlinks/_base.xt: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | skip_all( "Do Not Run on the Main base.t" ) if $0 =~ m{\Q/_base.xt\E$}; 4 | 5 | like $0, qr{symlink_to_base\.xt}, q[symlink preserved in $0]; 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/failure_cases/parse_error.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use goto::file( 4 | $ENV{FAILURE_DO_PASS} 5 | ? ['ok(1); done_testing;'] 6 | : ['ok(; done_testing;'] 7 | ); 8 | 9 | die "Should not see this!"; 10 | -------------------------------------------------------------------------------- /t/integration/preload/slow.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | #HARNESS-STAGE-SLOW 3 | 4 | ok($INC{'FAST.pm'}, "Preloaded fast"); 5 | is($ENV{T2_HARNESS_STAGE}, 'FAST', "Running in 'FAST' stage despite asking for 'SLOW'"); 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/integration/tapsubtest/test.tx: -------------------------------------------------------------------------------- 1 | # HARNESS-NO-STREAM 2 | use Test2::V0; 3 | use Test2::Tools::Subtest qw/subtest_buffered/; 4 | 5 | subtest_buffered buffered => sub { 6 | ok(1, "buffered ok"); 7 | }; 8 | 9 | done_testing; 10 | -------------------------------------------------------------------------------- /t2/data.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-DURATION-SHORT 3 | 4 | is( 5 | [], 6 | ["foo\n", "bar\n", "baz\n"], 7 | "Got data section" 8 | ); 9 | 10 | done_testing; 11 | 12 | __DATA__ 13 | foo 14 | bar 15 | baz 16 | -------------------------------------------------------------------------------- /t/integration/failure_cases/dupnums.tx: -------------------------------------------------------------------------------- 1 | my $out = < ( 8 | prefix => 'testplugin', 9 | type => 'b', 10 | ); 11 | 12 | 1; 13 | -------------------------------------------------------------------------------- /t2/magic_vars.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-DURATION-SHORT 3 | skip_all "Test breaks Devel::Cover db" if $ENV{T2_DEVEL_COVER}; 4 | 5 | $\ = '|'; 6 | $, = '|'; 7 | 8 | is($\, '|', 'set $\\'); 9 | is($,, '|', 'set $,'); 10 | 11 | done_testing 12 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/A.pm: -------------------------------------------------------------------------------- 1 | package Preload::A; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 7 | $PRELOAD::A //= 0; 8 | $PRELOAD::A++; 9 | } 10 | 11 | sub A { $PRELOAD::A } 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /t/integration/failure_cases/buffered_subtest_abrupt_end.tx: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $end = $ENV{FAILURE_DO_PASS} ? "}\n" : ""; 5 | 6 | print < sub { 4 | subtest bar => sub { 5 | subtest baz => sub { 6 | ok($ENV{FAILURE_DO_PASS}, "check env"); 7 | }; 8 | }; 9 | }; 10 | 11 | done_testing; 12 | -------------------------------------------------------------------------------- /t/child_exit_282.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | plan tests => 1; 4 | 5 | sub checks_exit_code { 6 | diag $?; 7 | 8 | if ( $? != 0 ) { 9 | die 'exit code is not 0'; 10 | } 11 | } 12 | 13 | ok(lives { checks_exit_code() }, 'exit code OK'); 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/integration/preload/lib/TestBadPreload.pm: -------------------------------------------------------------------------------- 1 | package TestBadPreload; 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::Harness::Runner::Preload; 6 | 7 | stage BAD => sub { 8 | default; 9 | preload "Test2::Harness::Preload::Does::Not::Exist"; 10 | }; 11 | 12 | 1; 13 | -------------------------------------------------------------------------------- /t2/exception.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-DURATION-SHORT 3 | 4 | my $file = __FILE__; 5 | my $line = __LINE__ + 1; 6 | sub throw { die("xxx") }; 7 | 8 | is( 9 | dies { throw() }, 10 | "xxx at $file line $line.\n", 11 | "Got exception as expected" 12 | ); 13 | 14 | done_testing; 15 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util/Term.t: -------------------------------------------------------------------------------- 1 | use Test2::V0 -target => 'Test2::Harness::Util::Term'; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok $CLASS => qw/USE_ANSI_COLOR/; 5 | 6 | imported_ok(qw/USE_ANSI_COLOR/); 7 | 8 | is(USE_ANSI_COLOR(), in_set(0, 1), "USE_ANSI_COLOR returns true or false"); 9 | 10 | done_testing; 11 | -------------------------------------------------------------------------------- /t2/subtests_buffered.t: -------------------------------------------------------------------------------- 1 | # HARNESS-NO-STREAM 2 | use strict; 3 | use warnings; 4 | use Test2::Tools::Tiny; 5 | use Test2::Tools::Subtest qw/subtest_buffered/; 6 | # HARNESS-DURATION-SHORT 7 | 8 | subtest_buffered foo => sub { 9 | subtest_buffered bar => sub { 10 | ok(1, 'baz'); 11 | }; 12 | }; 13 | 14 | done_testing; 15 | -------------------------------------------------------------------------------- /t2/subtests_streamed.t: -------------------------------------------------------------------------------- 1 | # HARNESS-NO-STREAM 2 | use strict; 3 | use warnings; 4 | use Test2::Tools::Tiny; 5 | use Test2::Tools::Subtest qw/subtest_streamed/; 6 | # HARNESS-DURATION-SHORT 7 | 8 | subtest_streamed foo => sub { 9 | subtest_streamed bar => sub { 10 | ok(1, 'baz'); 11 | }; 12 | }; 13 | 14 | done_testing; 15 | -------------------------------------------------------------------------------- /t/integration/failure_cases/buffered_subtest_abrupt_end_nested.tx: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $end = $ENV{FAILURE_DO_PASS} ? "}\n " : ""; 5 | 6 | print <rel2abs($file); 10 | 11 | require $file; 12 | 13 | ok(file_loaded(), "file loaded, proper namespace, etc"); 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/WarningA.pm: -------------------------------------------------------------------------------- 1 | package Preload::WarningA; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | local $.; 7 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 8 | $PRELOAD::WA //= 0; 9 | warn "Loaded ${ \__PACKAGE__ } again.\n" if $PRELOAD::WA++; 10 | } 11 | 12 | sub WA { $PRELOAD::WA } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/WarningB.pm: -------------------------------------------------------------------------------- 1 | package Preload::WarningB; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | local $.; 7 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 8 | $PRELOAD::WB //= 0; 9 | warn "Loaded ${ \__PACKAGE__ } again.\n" if $PRELOAD::WB++; 10 | } 11 | 12 | sub WB { $PRELOAD::WB } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /t2/vars.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | ok(defined($ENV{$_}), "env var $_ is set") for qw{ 4 | HARNESS_ACTIVE 5 | TEST2_HARNESS_ACTIVE 6 | TEST2_ACTIVE 7 | TEST_ACTIVE 8 | TEST2_RUN_DIR 9 | TEST2_JOB_DIR 10 | T2_HARNESS_JOB_IS_TRY 11 | T2_HARNESS_JOB_NAME 12 | T2_HARNESS_JOB_FILE 13 | }; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/B.pm: -------------------------------------------------------------------------------- 1 | package Preload::B; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 7 | $PRELOAD::B //= 0; 8 | $PRELOAD::B++; 9 | } 10 | 11 | sub B { $PRELOAD::B } 12 | 13 | die "PreDefined sub is missing!" unless __PACKAGE__->can('PreDefined'); 14 | 15 | 1; 16 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/ExporterA.pm: -------------------------------------------------------------------------------- 1 | package Preload::ExporterA; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 7 | $PRELOAD::EA //= 0; 8 | $PRELOAD::EA++; 9 | } 10 | 11 | use parent 'Exporter'; 12 | our @EXPORT_OK = 'EA'; 13 | 14 | sub EA { $PRELOAD::EA } 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/ExceptionA.pm: -------------------------------------------------------------------------------- 1 | package Preload::ExceptionA; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | local $.; 7 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 8 | $PRELOAD::ExA //= 0; 9 | die "Loaded ${ \__PACKAGE__ } again.\n" if $PRELOAD::ExA++; 10 | } 11 | 12 | sub ExA { $PRELOAD::ExA } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/ExporterB.pm: -------------------------------------------------------------------------------- 1 | package Preload::ExporterB; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 7 | $PRELOAD::EB //= 0; 8 | $PRELOAD::EB++; 9 | } 10 | 11 | our @EXPORT_OK = ('EB'); 12 | 13 | sub import { 1 } 14 | 15 | sub EB { $PRELOAD::EB } 16 | 17 | 1; 18 | 19 | -------------------------------------------------------------------------------- /t2/ending.t: -------------------------------------------------------------------------------- 1 | # HARNESS-DURATION-SHORT 2 | package FooBarBaz; 3 | use strict; 4 | use warnings; 5 | 6 | use Test2::V0; 7 | 8 | open(my $fh, '<', __FILE__) or die "Could not open this file!: $!"; 9 | my @end = <$fh>; 10 | close($fh); 11 | 12 | is($end[-1], 'done_testing', "no semicolon or newline is present at the end of this file"); 13 | 14 | done_testing -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/ExceptionB.pm: -------------------------------------------------------------------------------- 1 | package Preload::ExceptionB; 2 | use strict; 3 | use warnings; 4 | 5 | BEGIN { 6 | local $.; 7 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 8 | $PRELOAD::ExB //= 0; 9 | die "Loaded ${ \__PACKAGE__ } again.\n" if $PRELOAD::ExB++; 10 | } 11 | 12 | sub ExB { $PRELOAD::ExB } 13 | 14 | 1; 15 | 16 | -------------------------------------------------------------------------------- /t/integration/signals/abrt_or_iot.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use strict; 4 | use warnings; 5 | use Test::More; 6 | 7 | # note: this is going to fail if IOT is defined before... 8 | # %SIG = %SIG; will introduce a flapping behavior 9 | 10 | $SIG{'ABRT'} = sub { 11 | my ($sig) = @_; 12 | is $sig, 'ABRT'; 13 | }; 14 | 15 | kill 'ABRT', $$; 16 | 17 | done_testing; 18 | -------------------------------------------------------------------------------- /t/lib/App/Yath/Command/fake.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::fake; 2 | use strict; 3 | use warnings; 4 | 5 | use parent 'App::Yath::Command'; 6 | 7 | use App::Yath::Options; 8 | 9 | option_group {prefix => 'fake'}, sub { 10 | option($_, short => $_) for qw/x y z/; 11 | 12 | post sub { print "\n\nAAAA\n\n"; $main::POST_HOOK++ }; 13 | }; 14 | 15 | 1; 16 | -------------------------------------------------------------------------------- /t2/ipc_reexec.t: -------------------------------------------------------------------------------- 1 | # HARNESS-NO-FORK 2 | # HARNESS-DURATION-SHORT 3 | BEGIN { $INC{'Test2/Formatter/Stream.pm'} && exec($^X, $0); }; 4 | # Force into stdout 5 | BEGIN { 6 | delete $ENV{T2_STREAM_DIR}; 7 | delete $ENV{T2_FORMATTER}; 8 | } 9 | 10 | use Test::Builder; 11 | use Test2::V0; 12 | 13 | ok 1, "test runs correctly in IPC mode"; 14 | done_testing; 15 | -------------------------------------------------------------------------------- /t/integration/test-durations.json: -------------------------------------------------------------------------------- 1 | { 2 | "t/integration/test-durations/fast-01.tx": "SHORT", 3 | "t/integration/test-durations/fast-02.tx": "SHORT", 4 | "t/integration/test-durations/fast-03.tx": "SHORT", 5 | "t/integration/test-durations/fast-04.tx": "SHORT", 6 | "t/integration/test-durations/slow-01.tx": "LONG", 7 | "t/integration/test-durations/slow-02.tx": "LONG" 8 | } -------------------------------------------------------------------------------- /t/integration/projects/bar/t/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Bar; 3 | 4 | is(__FILE__, 't/pass.tx', "__FILE__ is set correctly"); 5 | 6 | like(dies { require Foo }, qr{Loaded Foo.pm from the wrong project}, "Using our own libs (Foo)"); 7 | like(dies { require Baz }, qr{Loaded Baz.pm from the wrong project}, "Using our own libs (Baz)"); 8 | 9 | ok(1, "Pass"); 10 | 11 | done_testing; 12 | -------------------------------------------------------------------------------- /t/integration/projects/baz/t/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Baz; 3 | 4 | is(__FILE__, 't/pass.tx', "__FILE__ is set correctly"); 5 | 6 | like(dies { require Foo }, qr{Loaded Foo.pm from the wrong project}, "Using our own libs (Foo)"); 7 | like(dies { require Bar }, qr{Loaded Bar.pm from the wrong project}, "Using our own libs (Bar)"); 8 | 9 | ok(1, "Pass"); 10 | 11 | done_testing; 12 | -------------------------------------------------------------------------------- /t/integration/projects/foo/t/pass.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Foo; 3 | 4 | is(__FILE__, 't/pass.tx', "__FILE__ is set correctly"); 5 | 6 | like(dies { require Bar }, qr{Loaded Bar.pm from the wrong project}, "Using our own libs (Bar)"); 7 | like(dies { require Baz }, qr{Loaded Baz.pm from the wrong project}, "Using our own libs (Baz)"); 8 | 9 | ok(1, "Pass"); 10 | 11 | done_testing; 12 | -------------------------------------------------------------------------------- /t/integration/smoke/lib/SmokePlugin.pm: -------------------------------------------------------------------------------- 1 | package SmokePlugin; 2 | use strict; 3 | use warnings; 4 | 5 | use parent 'App::Yath::Plugin'; 6 | 7 | sub munge_files { 8 | my $self = shift; 9 | my ($tests, $settings) = @_; 10 | 11 | for my $test (@$tests) { 12 | next unless $test->relative =~ m/[aceg]\.tx$/; 13 | $test->set_smoke; 14 | } 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /t/integration/stamps/lib/App/Yath/Plugin/TestPlugin.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Plugin::TestPlugin; 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::Harness::Util::JSON qw/encode_json/; 6 | 7 | use parent 'App::Yath::Plugin'; 8 | 9 | sub handle_event { 10 | my $self = shift; 11 | my ($event) = @_; 12 | 13 | die "Event did not have a stamp!" unless $event->stamp; 14 | } 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /t2/caller.t: -------------------------------------------------------------------------------- 1 | package FooBarBaz; 2 | use strict; 3 | use warnings; 4 | # HARNESS-DURATION-SHORT 5 | 6 | use Test2::V0; 7 | 8 | is([caller(0)], [], "No caller at the flat test level"); 9 | is(__PACKAGE__, 'FooBarBaz', "inside main package"); 10 | like(__FILE__, qr/caller\.t$/, "__FILE__ is correct"); 11 | is(__LINE__, 11, "Got the correct line number"); 12 | is($@, '', '$@ set to empty string'); 13 | 14 | done_testing; -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util/File/JSONL.t: -------------------------------------------------------------------------------- 1 | use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File::JSONL'; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok $CLASS; 5 | 6 | isa_ok($CLASS, 'Test2::Harness::Util::File'); 7 | isa_ok($CLASS, 'Test2::Harness::Util::File::Stream'); 8 | 9 | is($CLASS->decode('{"a":1}'), {a => 1}, "decode will decode json"); 10 | is($CLASS->encode({}), "{}\n", "encode will encode json and append a newline"); 11 | 12 | done_testing; 13 | -------------------------------------------------------------------------------- /t/integration/slots_per_job3.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-JOB-SLOTS 2 3 | 4 | skip_all "This test only works under Test2::Harness" unless $ENV{TEST2_HARNESS_ACTIVE}; 5 | 6 | ok(!$ENV{T2_HARNESS_JOB_CONCURRENCY}, "T2_HARNESS_JOB_CONCURRENCY is not set"); 7 | ok($ENV{T2_HARNESS_MY_JOB_CONCURRENCY}, "Have job concurrency set ($ENV{T2_HARNESS_MY_JOB_CONCURRENCY})"); 8 | is($ENV{T2_HARNESS_MY_JOB_CONCURRENCY}, 2, "Have job concurrency set (2)"); 9 | 10 | done_testing; 11 | -------------------------------------------------------------------------------- /t2/relative_paths.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use File::Spec; 3 | # HARNESS-DURATION-SHORT 4 | 5 | my $path = File::Spec->canonpath('t2/relative_paths.t'); 6 | 7 | skip_all "This test must be run from the project root." 8 | unless -f $path; 9 | 10 | is(__FILE__, $path, "__FILE__ is relative"); 11 | is(__FILE__, $0, "\$0 is relative"); 12 | 13 | sub { 14 | my ($pkg, $file) = caller(0); 15 | is($file, $path, "file in caller is relative"); 16 | }->(); 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/integration/slots_per_job.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | skip_all "This test only works under Test2::Harness" unless $ENV{TEST2_HARNESS_ACTIVE}; 4 | 5 | ok(!$ENV{T2_HARNESS_JOB_CONCURRENCY}, "T2_HARNESS_JOB_CONCURRENCY is not set"); 6 | ok($ENV{T2_HARNESS_MY_JOB_CONCURRENCY}, "Have job concurrency set ($ENV{T2_HARNESS_MY_JOB_CONCURRENCY})"); 7 | ok($ENV{T2_HARNESS_MY_JOB_CONCURRENCY} >= 1, "Have job concurrency set to a positive number ($ENV{T2_HARNESS_MY_JOB_CONCURRENCY})"); 8 | 9 | done_testing; 10 | -------------------------------------------------------------------------------- /t2/output.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use Test2::Require::AuthorTesting; 5 | 6 | # All these prints intentionally have no newlines 7 | print STDERR "STDERR Before any events"; 8 | print STDOUT "STDOUT Before any events"; 9 | 10 | ok(1, "pass"); 11 | 12 | print STDERR "STDERR Between events"; 13 | print STDOUT "STDOUT Between events"; 14 | 15 | ok(1, "pass"); 16 | 17 | print STDERR "STDERR after events"; 18 | print STDOUT "STDOUT after events"; 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/yath_script/.yath.rc: -------------------------------------------------------------------------------- 1 | 2 | ; comment! 3 | 4 | -Dpre_lib; comment! 5 | -D=rel(pre/xxx/lib) ;comment! 6 | -D=rel( pre/yyy/lib ) 7 | -pXXX 8 | -p YYY 9 | -D SPLIT 10 | --no-scan-plugins 11 | 12 | [test] 13 | -Itest_lib; comment! 14 | -I=rel(test/xxx/lib) ;comment! 15 | -I rel( test/yyy/lib ) 16 | --default-search relglob(../*.t) 17 | -xxxx;comment! 18 | foo 19 | bar 20 | baz bat 21 | 22 | [run] 23 | -Irun_lib 24 | -I=rel(run/xxx/lib) 25 | -I rel( run/yyy/lib ) 26 | -xxxx 27 | foo 28 | bar 29 | 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | perl: 3 | - "5.8" 4 | - "5.8-thr" 5 | - "5.10-thr" 6 | - "5.12" 7 | - "5.14" 8 | - "5.16" 9 | - "5.18" 10 | - "5.20-thr" 11 | - "5.22-thr" 12 | before_install: 13 | - git clone git://github.com/travis-perl/helpers ~/travis-perl-helpers 14 | - source ~/travis-perl-helpers/init --auto 15 | - cpan-install Test2 Test2::Suite 16 | notifications: 17 | slack: 18 | rooms: 19 | - perl-test2:kI7LQjG4iRjIqA2JTl770A5F#general 20 | on_success: change 21 | 22 | -------------------------------------------------------------------------------- /t/integration/coverage/once.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Plugin::Cover; 3 | use Path::Tiny; 4 | 5 | use Test2::Harness::Util::JSON qw/encode_json/; 6 | 7 | STDIN->blocking(0); 8 | 9 | print "INPUT ${ \__FILE__ }: " . encode_json({ 10 | env => {map { ($_ => $ENV{$_}) } grep { m/^COVER_TEST_/ } keys %ENV}, 11 | argv => [@ARGV], 12 | stdin => join('' => ), 13 | }) . "\n"; 14 | 15 | ok(1); 16 | 17 | Test2::Plugin::Cover->set_root(path('t/integration/coverage/lib')->realpath); 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t2/relative_paths_no_fork.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use File::Spec; 3 | # HARNESS-NO-FORK 4 | # HARNESS-DURATION-SHORT 5 | 6 | my $path = File::Spec->canonpath('t2/relative_paths_no_fork.t'); 7 | 8 | skip_all "This test must be run from the project root." 9 | unless -f $path; 10 | 11 | is(__FILE__, $path, "__FILE__ is relative"); 12 | is(__FILE__, $0, "\$0 is relative"); 13 | 14 | sub { 15 | my ($pkg, $file) = caller(0); 16 | is($file, $path, "file in caller is relative"); 17 | }->(); 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/yath_script/nested/.yath.user.rc: -------------------------------------------------------------------------------- 1 | 2 | ; comment! 3 | 4 | -Dpre_user_lib; comment! 5 | -D=rel(pre/xxx/user/lib) ;comment! 6 | -D=rel( pre/yyy/user/lib ) 7 | -pUSER_XXX 8 | -p USER_YYY 9 | 10 | [test] 11 | -Itest_user_lib; comment! 12 | -I=rel(test/xxx/user/lib) ;comment! 13 | -I rel( test/yyy/user/lib ) 14 | -user_xxxx;comment! 15 | user_foo 16 | user_bar 17 | user_baz user_bat 18 | 19 | [run] 20 | -Irun_user_lib 21 | -I=rel(run/xxx/user/lib) 22 | -I rel( run/yyy/user/lib ) 23 | -user_xxxx 24 | user_foo 25 | user_bar 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.bak 3 | *~ 4 | *.old 5 | *.tar.gz 6 | *.ERR 7 | *.orig 8 | *.rej 9 | Makefile 10 | blib/ 11 | pm_to_blib 12 | Build 13 | _build 14 | cover_db/ 15 | MYMETA.* 16 | /Test2-*/ 17 | /.build 18 | TODO 19 | t2_lib 20 | POD_TEMPLATE.POD 21 | /old*/ 22 | event-log-* 23 | .yath-persist.json 24 | test-logs/ 25 | /pt/ 26 | /tt/ 27 | /*.jsonl 28 | t2/non_perl/test.binary 29 | t/integration/test-broken-symlinks/broken-symlink.tx 30 | coverage.json 31 | lastlog.* 32 | *.test_info.json 33 | *.test_info.*.json 34 | .testsharedjobslots.yml 35 | -------------------------------------------------------------------------------- /t/integration/coverage/x.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Plugin::Cover; 3 | use Path::Tiny; 4 | 5 | use Test2::Harness::Util::JSON qw/encode_json/; 6 | 7 | STDIN->blocking(0); 8 | 9 | Test2::Plugin::Cover->set_root(path('t/integration/coverage/lib')->realpath); 10 | 11 | print "INPUT ${ \__FILE__ }: " . encode_json({ 12 | env => {map { ($_ => $ENV{$_}) } grep { m/^COVER_TEST_/ } keys %ENV}, 13 | argv => [@ARGV], 14 | stdin => join('' => ), 15 | }) . "\n"; 16 | 17 | require Bx; 18 | 19 | ok(1); 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/integration/slots_per_job2.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use List::Util qw/min/; 3 | # HARNESS-JOB-SLOTS 1 3 4 | 5 | skip_all "This test only works under Test2::Harness" unless $ENV{TEST2_HARNESS_ACTIVE}; 6 | 7 | ok(!$ENV{T2_HARNESS_JOB_CONCURRENCY}, "T2_HARNESS_JOB_CONCURRENCY is not set"); 8 | ok($ENV{T2_HARNESS_MY_JOB_CONCURRENCY}, "Have job concurrency set ($ENV{T2_HARNESS_MY_JOB_CONCURRENCY})"); 9 | is($ENV{T2_HARNESS_MY_JOB_CONCURRENCY}, in_set(1, 2, 3), "Have job concurrency set ($ENV{T2_HARNESS_MY_JOB_CONCURRENCY})"); 10 | 11 | done_testing; 12 | -------------------------------------------------------------------------------- /t/integration/coverage/b.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Plugin::Cover; 3 | use Path::Tiny; 4 | 5 | use Test2::Harness::Util::JSON qw/encode_json/; 6 | 7 | STDIN->blocking(0); 8 | 9 | Test2::Plugin::Cover->set_root(path('t/integration/coverage/lib')->realpath); 10 | 11 | print "INPUT ${ \__FILE__ }: " . encode_json({ 12 | env => {map { ($_ => $ENV{$_}) } grep { m/^COVER_TEST_/ } keys %ENV}, 13 | argv => [@ARGV], 14 | stdin => join('' => ), 15 | }) . "\n"; 16 | 17 | use Bx; 18 | 19 | is(Bx->b, 'b', "Got b"); 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/integration/coverage/open.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Plugin::Cover; 3 | use Path::Tiny; 4 | 5 | use Test2::Harness::Util::JSON qw/encode_json/; 6 | 7 | STDIN->blocking(0); 8 | 9 | Test2::Plugin::Cover->set_root(path('t/integration/coverage/lib')->realpath); 10 | 11 | print "INPUT ${ \__FILE__ }: " . encode_json({ 12 | env => {map { ($_ => $ENV{$_}) } grep { m/^COVER_TEST_/ } keys %ENV}, 13 | argv => [@ARGV], 14 | stdin => join('' => ), 15 | }) . "\n"; 16 | 17 | 18 | open(my $fh, '<', "t/integration/coverage/lib/Bx.pm"); 19 | 20 | ok(1); 21 | 22 | done_testing; 23 | -------------------------------------------------------------------------------- /t/integration/retry-timeout/retry.tx: -------------------------------------------------------------------------------- 1 | # HARNESS-TIMEOUT-EVENT 5 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::V0; 6 | use Test2::API qw/test2_formatter/; 7 | 8 | pass("Test Start"); 9 | 10 | $ENV{T2_HARNESS_JOB_IS_TRY} //= 0; 11 | $ENV{FAIL_ONCE} //= 0; 12 | $ENV{FAIL_ALWAYS} //= 0; 13 | 14 | diag "JOB_IS_TRY = $ENV{T2_HARNESS_JOB_IS_TRY}"; 15 | diag "FAIL_ONCE = $ENV{FAIL_ONCE}"; 16 | diag "FAIL_ALWAYS = $ENV{FAIL_ALWAYS}"; 17 | 18 | if ( $ENV{FAIL_ONCE} && $ENV{T2_HARNESS_JOB_IS_TRY} < 1 ) { 19 | sleep 1 while 1; 20 | } 21 | 22 | pass("Final Test"); 23 | 24 | done_testing(); 25 | -------------------------------------------------------------------------------- /t/integration/test-inc/check-INC.tx: -------------------------------------------------------------------------------- 1 | package My::Simple::Test; 2 | 3 | use Test2::V0; 4 | 5 | my $has_dot_in_inc = grep { $_ eq '.' } @INC; 6 | ok !$has_dot_in_inc, q['.' is not in @INC run with --no-unsafe-inc]; 7 | 8 | { # relative path in @INC 9 | my @relative_path = grep { index( $_, '/', 0 ) != 0 } @INC; 10 | is \@relative_path, [], q[@INC does not contain relative path]; 11 | } 12 | 13 | { # check elative path in %INC 14 | my @relative_path = grep { index( $_, '/', 0 ) != 0 } sort values %INC; 15 | is \@relative_path, [], q[%INC does not contain relative path values]; 16 | } 17 | 18 | done_testing; 19 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util/File/Value.t: -------------------------------------------------------------------------------- 1 | use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File::Value'; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok $CLASS; 5 | 6 | isa_ok($CLASS, 'Test2::Harness::Util::File'); 7 | 8 | my $one = $CLASS->new(name => __FILE__); 9 | 10 | my $val = $one->read; 11 | chomp(my $no_tail = $val); 12 | is($val, $no_tail, "trailing newline was removed from the value"); 13 | 14 | $val = $one->read_line; 15 | is( 16 | $val, 17 | "use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File::Value';", 18 | "got line, no newline" 19 | ); 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/integration/test-w.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use App::Yath::Tester qw/yath/; 4 | 5 | my $dir = __FILE__; 6 | $dir =~ s{\.t$}{}g; 7 | $dir =~ s{^\./}{}; 8 | 9 | # assert that, regardless of order, the `perl -w` shebang only applies 10 | # to the test file it appears in; see 11 | # https://github.com/Test-More/Test2-Harness/issues/266 12 | 13 | yath( 14 | command => 'test', 15 | args => ["$dir/a.tx", "$dir/b.tx", '--ext=tx'], 16 | exit => 0, 17 | ); 18 | 19 | yath( 20 | command => 'test', 21 | args => ["$dir/b.tx", "$dir/a.tx", '--ext=tx'], 22 | exit => 0, 23 | ); 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/unit/App/Yath/Plugin.t: -------------------------------------------------------------------------------- 1 | use Test2::V0 -target => 'App::Yath::Plugin'; 2 | 3 | isa_ok($CLASS, ['Test2::Harness::Plugin'], "Subclasses Test2::Harness::Plugin"); 4 | 5 | can_ok($CLASS, ['finish'], "finish() is defined"); 6 | is([$CLASS->finish], [], "finish returns an empty list in list context"); 7 | is($CLASS->finish, undef, "finish returns undef in scalar context"); 8 | 9 | ok(!$CLASS->can('sort_files'), "sort_files is not defined by default"); 10 | ok(!$CLASS->can('sort_files_2'), "sort_files_2 is not defined by default"); 11 | ok(!$CLASS->can('handle_event'), "handle_event is not defined by default"); 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /t/0-load_all.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Find; 4 | use Test2::Harness; 5 | use Test2::Harness::Util qw/file2mod/; 6 | 7 | find(\&wanted, 'lib/'); 8 | 9 | sub wanted { 10 | my $file = $File::Find::name; 11 | return unless $file =~ m/\.pm$/; 12 | 13 | $file =~ s{^.*lib/}{}g; 14 | my $ok = eval { require($file); 1 }; 15 | my $err = $@; 16 | ok($ok, "require $file", $ok ? () : $err); 17 | 18 | my $mod = file2mod($file); 19 | my $sym = "$mod\::VERSION"; 20 | no strict 'refs'; 21 | is($$sym, $Test2::Harness::VERSION, "Package $mod ($file) has the version number"); 22 | }; 23 | 24 | done_testing; 25 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/projects.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::projects; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use parent 'App::Yath::Command::test'; 8 | use Test2::Harness::Util::HashBase; 9 | 10 | sub summary { "Run tests for multiple projects" } 11 | sub cli_args { "[--] projects_dir [::] [arguments to test scripts]" } 12 | 13 | sub description { 14 | return <<" EOT"; 15 | This command will run all the tests for each project within a parent directory. 16 | EOT 17 | } 18 | 19 | sub finder_args {(multi_project => 1)} 20 | 21 | 1; 22 | 23 | __END__ 24 | 25 | =head1 POD IS AUTO-GENERATED 26 | 27 | -------------------------------------------------------------------------------- /t/integration/includes/default.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | require App::Yath; 4 | 5 | use File::Spec; 6 | 7 | my @parts = File::Spec->splitpath(File::Spec->rel2abs(__FILE__)); 8 | pop @parts; 9 | my $path = File::Spec->catpath(@parts); 10 | 11 | like( 12 | \@INC, 13 | [ 14 | App::Yath->app_path, 15 | File::Spec->catdir($path, 'lib'), 16 | File::Spec->catdir($path, 'blib', 'lib'), 17 | File::Spec->catdir($path, 'blib', 'arch'), 18 | ], 19 | "Added lib, blib/lib, and blib/arch to the front of the line" 20 | ); 21 | 22 | is($ENV{PERL5LIB}, $ENV{OLD_PERL5LIB}, "PERL5LIB has not been modified"); 23 | 24 | done_testing; 25 | -------------------------------------------------------------------------------- /t/integration/signals.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Require::AuthorTesting; 3 | 4 | use File::Temp qw/tempdir/; 5 | use File::Spec; 6 | 7 | use Test2::Harness::Util::File::JSONL; 8 | use App::Yath::Tester qw/yath/; 9 | 10 | my $dir = __FILE__; 11 | $dir =~ s{\.t$}{}g; 12 | $dir =~ s{^\./}{}; 13 | 14 | for ( 1..10 ) { 15 | # the tests are flapping when using something like '%INC = %INC'.... 16 | # make sure the issue is fixed by running them a few times 17 | my $out = yath( 18 | prefix => "Try $_: ", 19 | command => 'test', 20 | args => [$dir], 21 | log => 0, 22 | exit => 0, 23 | ); 24 | } 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/integration/encoding/no-plugin.tx: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | note "valid note [“”\xff\xff]"; 7 | note "valid note [“”]"; 8 | 9 | diag "valid diag [“”\xff\xff]"; 10 | diag "valid diag [“”]"; 11 | 12 | print "valid stdout [“”\xff\xff]\n"; 13 | print "valid stdout [“”]\n"; 14 | 15 | print STDERR "valid stderr [“”\xff\xff]\n"; 16 | print STDERR "valid stderr [“”]\n"; 17 | 18 | ok 1, "valid ok [“”\xff\xff]"; 19 | ok 1, "valid ok [“”]"; 20 | 21 | print STDOUT "STDOUT: Mākaha\n"; 22 | print STDERR "STDERR: Mākaha\n"; 23 | diag "DIAG: Mākaha"; 24 | note "NOTE: Mākaha"; 25 | ok(1, "ASSERT: Mākaha"); 26 | ok(1, "І ще трохи"); 27 | 28 | done_testing(); 29 | 30 | -------------------------------------------------------------------------------- /t/integration/includes/default-i.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Spec; 4 | 5 | my @parts = File::Spec->splitpath(File::Spec->rel2abs(__FILE__)); 6 | pop @parts; 7 | my $path = File::Spec->catpath(@parts); 8 | 9 | require App::Yath; 10 | 11 | like( 12 | \@INC, 13 | [ 14 | App::Yath->app_path, 15 | File::Spec->catdir($path, 'xyz'), 16 | File::Spec->catdir($path, 'lib'), 17 | File::Spec->catdir($path, 'blib', 'lib'), 18 | File::Spec->catdir($path, 'blib', 'arch'), 19 | ], 20 | "Added lib, blib/lib, and blib/arch AFTER the -Ixyz" 21 | ); 22 | 23 | is($ENV{PERL5LIB}, $ENV{OLD_PERL5LIB}, "PERL5LIB has not been modified"); 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/integration/encoding/plugin.tx: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | use Test2::Plugin::UTF8; 6 | 7 | note "valid note [“”\xff\xff]"; 8 | note "valid note [“”]"; 9 | 10 | diag "valid diag [“”\xff\xff]"; 11 | diag "valid diag [“”]"; 12 | 13 | print "valid stdout [“”\xff\xff]\n"; 14 | print "valid stdout [“”]\n"; 15 | 16 | print STDERR "valid stderr [“”\xff\xff]\n"; 17 | print STDERR "valid stderr [“”]\n"; 18 | 19 | ok 1, "valid ok [“”\xff\xff]"; 20 | ok 1, "valid ok [“”]"; 21 | 22 | print STDOUT "STDOUT: Mākaha\n"; 23 | print STDERR "STDERR: Mākaha\n"; 24 | diag "DIAG: Mākaha"; 25 | note "NOTE: Mākaha"; 26 | ok(1, "ASSERT: Mākaha"); 27 | ok(1, "І ще трохи"); 28 | 29 | done_testing(); 30 | -------------------------------------------------------------------------------- /t/integration/includes/dot-last.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Spec; 4 | 5 | my @parts = File::Spec->splitpath(File::Spec->rel2abs(__FILE__)); 6 | pop @parts; 7 | my $path = File::Spec->catpath(@parts); 8 | 9 | require App::Yath; 10 | 11 | like( 12 | \@INC, 13 | [ 14 | App::Yath->app_path, 15 | File::Spec->catdir($path, 'xyz'), 16 | File::Spec->catdir($path, 'lib'), 17 | File::Spec->catdir($path, 'blib', 'lib'), 18 | File::Spec->catdir($path, 'blib', 'arch'), 19 | ], 20 | "Added all via cli, in order" 21 | ); 22 | 23 | is($INC[-1], '.', "Dot added last"); 24 | 25 | is($ENV{PERL5LIB}, $ENV{OLD_PERL5LIB}, "PERL5LIB has not been modified"); 26 | 27 | done_testing; 28 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload/Churn.pm: -------------------------------------------------------------------------------- 1 | package Preload::Churn; 2 | 3 | our $counter; 4 | $counter //= 0; 5 | die "Counter incremented!" if $counter; 6 | $counter++; 7 | 8 | # HARNESS-CHURN-START 9 | our $counter2; 10 | $counter2 //= 0; 11 | print "$$ $0 - Churn 1\n"; 12 | $counter2++; 13 | my $foo = "foo $counter2"; 14 | sub foo { $foo } 15 | print "$$ $0 - FOO: " . Preload::Churn->foo . "\n"; 16 | # HARNESS-CHURN-STOP 17 | 18 | # HARNESS-CHURN-START 19 | print "$$ $0 - Churn 2\n"; 20 | # HARNESS-CHURN-STOP 21 | 22 | # HARNESS-CHURN-START 23 | our $counter3; 24 | $counter3 //= 0; 25 | die "$$ $0 - Died on count $counter3\n" if $counter3++; 26 | print "$$ $0 - Churn 3\n"; 27 | $counter3++; 28 | # HARNESS-CHURN-STOP 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util/File/JSON.t: -------------------------------------------------------------------------------- 1 | use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File::JSON'; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok $CLASS; 5 | 6 | isa_ok($CLASS, 'Test2::Harness::Util::File'); 7 | 8 | my $one = $CLASS->new(name => 'fake'); 9 | 10 | is($one->decode('{"a":1}'), {a => 1}, "decode will decode json"); 11 | is($one->encode({}), "{}", "encode will encode json"); 12 | 13 | like( 14 | dies { $one->reset }, 15 | qr/line reading is disabled for json files/, 16 | "Got expected exception for reset()" 17 | ); 18 | 19 | like( 20 | dies { $one->read_line }, 21 | qr/line reading is disabled for json files/, 22 | "Got expected exception for read_line()" 23 | ); 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/integration/tapsubtest.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use App::Yath::Tester qw/yath/; 4 | 5 | my $dir = __FILE__; 6 | $dir =~ s{\.t$}{}g; 7 | $dir =~ s{^\./}{}; 8 | 9 | yath( 10 | command => 'test', 11 | args => [$dir, '--ext=tx', '-v'], 12 | exit => 0, 13 | test => sub { 14 | my $todo = todo "FIXME #216"; 15 | my $out = shift; 16 | 17 | chomp(my $want = <<' EOT'); 18 | [ PASS ] job 1 +~buffered 19 | [ PASS ] job 1 + buffered ok 20 | [ PLAN ] job 1 | Expected assertions: 1 21 | job 1 ^ 22 | [ PLAN ] job 1 Expected assertions: 1 23 | EOT 24 | 25 | like($out->{output}, qr{\Q$want\E}, "Got the desired output"); 26 | }, 27 | ); 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /t/integration/retry/retry.tx: -------------------------------------------------------------------------------- 1 | # HARNESS-DURATION-SHORT 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::V0; 6 | use Test2::API qw/test2_formatter/; 7 | 8 | ok(1, "Minimal result"); 9 | 10 | sub { 11 | my $ctx = context(); 12 | 13 | diag "Formatter: " . test2_formatter(); 14 | 15 | $ctx->release; 16 | }->(); 17 | 18 | 19 | $ENV{T2_HARNESS_JOB_IS_TRY} //= 0; 20 | $ENV{FAIL_ONCE} //= 0; 21 | $ENV{FAIL_ALWAYS} //= 0; 22 | 23 | diag "JOB_IS_TRY = $ENV{T2_HARNESS_JOB_IS_TRY}"; 24 | diag "FAIL_ONCE = $ENV{FAIL_ONCE}"; 25 | diag "FAIL_ALWAYS = $ENV{FAIL_ALWAYS}"; 26 | 27 | ok(0, "Should fail once") if $ENV{FAIL_ONCE} && $ENV{T2_HARNESS_JOB_IS_TRY} < 1; 28 | ok(0, "Should fail always") if $ENV{FAIL_ALWAYS}; 29 | 30 | done_testing(); 31 | -------------------------------------------------------------------------------- /t/integration/retry-symlinks/retry.tx: -------------------------------------------------------------------------------- 1 | # HARNESS-DURATION-SHORT 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::V0; 6 | use Test2::API qw/test2_formatter/; 7 | 8 | ok(1, "Minimal result"); 9 | 10 | sub { 11 | my $ctx = context(); 12 | 13 | diag "Formatter: " . test2_formatter(); 14 | 15 | $ctx->release; 16 | }->(); 17 | 18 | 19 | $ENV{T2_HARNESS_JOB_IS_TRY} //= 0; 20 | $ENV{FAIL_ONCE} //= 0; 21 | $ENV{FAIL_ALWAYS} //= 0; 22 | 23 | diag "JOB_IS_TRY = $ENV{T2_HARNESS_JOB_IS_TRY}"; 24 | diag "FAIL_ONCE = $ENV{FAIL_ONCE}"; 25 | diag "FAIL_ALWAYS = $ENV{FAIL_ALWAYS}"; 26 | 27 | ok(0, "Should fail once") if $ENV{FAIL_ONCE} && $ENV{T2_HARNESS_JOB_IS_TRY} < 1; 28 | ok(0, "Should fail always") if $ENV{FAIL_ALWAYS}; 29 | 30 | done_testing(); 31 | -------------------------------------------------------------------------------- /t2/subtests.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # HARNESS-DURATION-MEDIUM 3 | 4 | use Test2::V0; 5 | use Time::HiRes qw/sleep/; 6 | use Test2::Tools::AsyncSubtest; 7 | 8 | ok(1, "pass"); 9 | 10 | my $astA = async_subtest 'ast A'; 11 | 12 | $astA->run(sub { ok(1, "ast A 1") }); 13 | 14 | subtest out => sub { 15 | ok(1, "pass"); 16 | ok(1, "pass"); 17 | 18 | my $astB = async_subtest 'ast B'; 19 | 20 | $astB->run(sub { ok(1, "ast B 1") }); 21 | $astA->run(sub { ok(1, "ast A 2") }); 22 | 23 | $astB->finish; 24 | 25 | subtest in => sub { 26 | for (1 .. 10) { 27 | ok(1, "pass $_"); 28 | sleep 0.1; 29 | } 30 | }; 31 | 32 | ok(1, "pass"); 33 | ok(1, "pass"); 34 | }; 35 | 36 | $astA->finish; 37 | 38 | done_testing; 39 | -------------------------------------------------------------------------------- /t2/utf8.t: -------------------------------------------------------------------------------- 1 | use utf8; 2 | use Test2::V0; 3 | use Test2::Plugin::UTF8; 4 | use Test2::API qw/test2_stack/; 5 | use Test2::Harness::Util::JSON qw/decode_json/; 6 | use Test2::Util qw/get_tid ipc_separator/; 7 | # HARNESS-DURATION-SHORT 8 | 9 | test2_stack()->top; 10 | my ($hub) = test2_stack()->all(); 11 | my $fmt = $hub->format; 12 | skip_all "This test requires the stream formatter" 13 | unless $fmt && $fmt->isa('Test2::Formatter::Stream'); 14 | 15 | ok(1, "І ще трохи"); 16 | 17 | my $file = File::Spec->catfile($fmt->dir, join(ipc_separator() => 'events', $$, 0) . ".jsonl"); 18 | open(my $fh, '<:utf8', $file) or die "Could not open events file: $!"; 19 | 20 | my @lines = <$fh>; 21 | 22 | like($lines[-1], qr/\QІ ще трохи\E/, "Wrote utf8, not double encoded"); 23 | 24 | done_testing; 25 | -------------------------------------------------------------------------------- /t/integration/includes/order-ibili.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Spec; 4 | 5 | my @parts = File::Spec->splitpath(File::Spec->rel2abs(__FILE__)); 6 | pop @parts; 7 | my $path = File::Spec->catpath(@parts); 8 | 9 | require App::Yath; 10 | 11 | like( 12 | \@INC, 13 | [ 14 | App::Yath->app_path, 15 | 16 | File::Spec->catdir($path, 'a'), 17 | 18 | File::Spec->catdir($path, 'blib', 'lib'), 19 | File::Spec->catdir($path, 'blib', 'arch'), 20 | 21 | File::Spec->catdir($path, 'b'), 22 | 23 | File::Spec->catdir($path, 'lib'), 24 | 25 | File::Spec->catdir($path, 'c'), 26 | ], 27 | "Added all via cli, in order" 28 | ); 29 | 30 | is($ENV{PERL5LIB}, $ENV{OLD_PERL5LIB}, "PERL5LIB has not been modified"); 31 | 32 | done_testing; 33 | -------------------------------------------------------------------------------- /t/integration/includes/order-ilibi.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Spec; 4 | 5 | my @parts = File::Spec->splitpath(File::Spec->rel2abs(__FILE__)); 6 | pop @parts; 7 | my $path = File::Spec->catpath(@parts); 8 | 9 | require App::Yath; 10 | 11 | like( 12 | \@INC, 13 | [ 14 | App::Yath->app_path, 15 | 16 | File::Spec->catdir($path, 'a'), 17 | 18 | File::Spec->catdir($path, 'lib'), 19 | 20 | File::Spec->catdir($path, 'b'), 21 | 22 | File::Spec->catdir($path, 'blib', 'lib'), 23 | File::Spec->catdir($path, 'blib', 'arch'), 24 | 25 | File::Spec->catdir($path, 'c'), 26 | ], 27 | "Added all via cli, in order" 28 | ); 29 | 30 | is($ENV{PERL5LIB}, $ENV{OLD_PERL5LIB}, "PERL5LIB has not been modified"); 31 | 32 | done_testing; 33 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | skip_tags: true 2 | 3 | cache: 4 | - C:\strawberry 5 | 6 | install: 7 | - if not exist "C:\strawberry" cinst strawberryperl -y 8 | - set PATH=C:\strawberry\perl\bin;C:\strawberry\perl\site\bin;C:\strawberry\c\bin;%PATH% 9 | - cd C:\projects\%APPVEYOR_PROJECT_NAME% 10 | - cpanm -n Dist::Zilla 11 | - dzil authordeps --missing | cpanm -n 12 | - dzil listdeps --author --missing | cpanm 13 | 14 | build_script: 15 | - perl -e 2 16 | 17 | test_script: 18 | - dzil test 19 | 20 | notifications: 21 | - provider: Slack 22 | auth_token: 23 | secure: 1XmVVszAQyTtMdNkyWup8p7AC9iqXkMl6QMchq3Xu7L7rCzYgjjlS/mas+bfp3ouyjPKnoh01twl4eB0Xs/1Ig== 24 | channel: '#general' 25 | on_build_success: false 26 | on_build_failure: true 27 | on_build_status_changed: true 28 | 29 | -------------------------------------------------------------------------------- /t/lib/App/Yath/Plugin/Fail.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Plugin::Test; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '0.001016'; 6 | 7 | use parent 'App::Yath::Plugin'; 8 | 9 | my %CALLS; 10 | 11 | sub options { push @{$CALLS{options}} => [@_]; return } 12 | sub pre_init { push @{$CALLS{pre_init}} => [@_]; return } 13 | sub post_init { push @{$CALLS{post_init}} => [@_]; return } 14 | sub find_files { push @{$CALLS{find_files}} => [@_]; return } 15 | sub block_default_search { push @{$CALLS{block_default_search}} => [@_]; return } 16 | 17 | sub CLEAR_CALLS { %CALLS = () } 18 | 19 | sub GET_CALLS { 20 | return { %CALLS } 21 | } 22 | 23 | use Carp qw/confess/; 24 | confess "Should not see this"; 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /t/integration/stamps.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | use Test2::Harness::Util::File::JSONL; 8 | 9 | use Test2::Harness::Util::JSON qw/decode_json/; 10 | 11 | my $dir = __FILE__; 12 | $dir =~ s{\.t$}{}g; 13 | $dir =~ s{^\./}{}; 14 | 15 | yath( 16 | command => 'test', 17 | args => [$dir, '--ext=tx', '-A', '--no-plugins', '-pTestPlugin', '-v'], 18 | exit => T(), 19 | log => 1, 20 | test => sub { 21 | my $out = shift; 22 | 23 | while (my @events = $out->{log}->poll()) { 24 | for my $event (@events) { 25 | last unless $event; 26 | ok($event->{stamp}, "Event had a timestamp"); 27 | } 28 | } 29 | }, 30 | ); 31 | 32 | done_testing; 33 | -------------------------------------------------------------------------------- /t2/lib/App/Yath/Plugin/SelfTest.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Plugin::SelfTest; 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::Harness::TestFile; 6 | 7 | use IPC::Cmd qw/can_run/; 8 | use parent 'App::Yath::Plugin'; 9 | 10 | sub find_files { 11 | my ($plugin, $run, $search) = @_; 12 | 13 | return if ($search && !grep { m{^(./)?t2(/non_perl(/(.*)?)?)?} } @$search); 14 | 15 | my @out; 16 | 17 | if (can_run('/usr/bin/bash')) { 18 | push @out => Test2::Harness::TestFile->new(file => "t2/non_perl/test.sh"); 19 | } 20 | 21 | if (can_run('gcc')) { 22 | system('gcc', '-o' => 't2/non_perl/test.binary', 't2/non_perl/test.c') and die "Failed to compile t2/non_perl/test.c"; 23 | push @out => Test2::Harness::TestFile->new(file => "t2/non_perl/test.binary"); 24 | } 25 | 26 | return @out; 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /t/integration/coverage/lib/Manager.pm: -------------------------------------------------------------------------------- 1 | package Manager; 2 | use strict; 3 | use warnings; 4 | 5 | sub test_parameters { 6 | my $class = shift; 7 | my ($test, $coverage_data) = @_; 8 | 9 | my %seen; 10 | my @subtests; 11 | 12 | for my $set (values %$coverage_data) { 13 | for my $value (@$set) { 14 | next unless ref $value eq 'HASH'; 15 | my $subtest = $value->{subtest} or next; 16 | next if $seen{$subtest}++; 17 | push @subtests => $subtest; 18 | } 19 | } 20 | 21 | return unless @subtests; 22 | 23 | @subtests = sort @subtests; 24 | 25 | return { 26 | run => 1, 27 | env => { COVER_TEST_SUBTESTS => join(", " => @subtests) }, 28 | argv => \@subtests, 29 | stdin => join("\n" => @subtests) . "\n", 30 | }; 31 | } 32 | 33 | 1; 34 | -------------------------------------------------------------------------------- /test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # HARNESS-NO-RUN 3 | use strict; 4 | use warnings; 5 | 6 | use lib 'lib'; 7 | use App::Yath::Util qw/find_yath/; 8 | 9 | print "1..2\n"; 10 | 11 | $ENV{'YATH_SELF_TEST'} = 1; 12 | system($^X, find_yath(), '-D', 'test', '--qvf', '-r1', '--default-search' => './t', '--default-search' => './t2', @ARGV); 13 | my $exit1 = $?; 14 | 15 | $ENV{T2_NO_FORK} = 1; 16 | system($^X, find_yath(), '-D', 'test', '--qvf', '-r1', '--default-search' => './t', '--default-search' => './t2', @ARGV); 17 | my $exit2 = $?; 18 | 19 | print "not " if $exit1; 20 | print "ok 1 - Passed tests when run by yath (allow fork)\n"; 21 | print STDERR "yath exited with $exit1" if $exit1; 22 | 23 | print "not " if $exit2; 24 | print "ok 2 - Passed tests when run by yath (no fork)\n"; 25 | print STDERR "yath exited with $exit2" if $exit2; 26 | 27 | exit($exit1 || $exit2 ? 255 : 0); 28 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util/JSON.t: -------------------------------------------------------------------------------- 1 | use Test2::Bundle::Extended -target => 'Test2::Harness::Util::JSON'; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok $CLASS; 5 | 6 | imported_ok(qw{ 7 | JSON 8 | encode_json decode_json 9 | encode_pretty_json encode_canon_json 10 | }); 11 | 12 | ok(JSON(), "Have JSON constant"); 13 | 14 | can_ok(JSON(), ['new'], "JSON returns a class (" . JSON() . ")"); 15 | 16 | my $struct = { a => 1, b => 2 }; 17 | for my $encode_name (qw/encode_json encode_pretty_json encode_canon_json/) { 18 | is( 19 | decode_json(__PACKAGE__->can($encode_name)->($struct)), 20 | $struct, 21 | "Round Trip $encode_name+decode" 22 | ); 23 | 24 | is( 25 | decode_json(__PACKAGE__->can($encode_name)->(undef)), 26 | undef, 27 | "undef/null round-trip $encode_name+decode" 28 | ); 29 | } 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/integration/preload/ccc.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | #HARNESS-STAGE-CCC 3 | 4 | is($ENV{T2_HARNESS_STAGE}, 'CCC', "Running in stage 'CCC'"); 5 | ok($INC{'CCC.pm'}, "Preloaded CCC"); 6 | 7 | is( 8 | [sort { $TestPreload::HOOKS{$a}->[0] <=> $TestPreload::HOOKS{$b}->[0] } keys %TestPreload::HOOKS], 9 | [qw/INIT PRE_FORK POST_FORK PRE_LAUNCH/], 10 | "Hooks happened in order" 11 | ); 12 | 13 | is( 14 | $TestPreload::HOOKS{POST_FORK}->[1], 15 | $TestPreload::HOOKS{PRE_LAUNCH}->[1], 16 | "POST_FORK and PRE_LAUNCH happened in the same PID" 17 | ); 18 | 19 | isnt( 20 | $TestPreload::HOOKS{POST_FORK}->[1], 21 | $TestPreload::HOOKS{INIT}->[1], 22 | "POST_FORK and INIT are not in the same PID" 23 | ); 24 | 25 | isnt( 26 | $TestPreload::HOOKS{POST_FORK}->[1], 27 | $TestPreload::HOOKS{PRE_FORK}->[1], 28 | "POST_FORK and PRE_FORK are not in the same PID" 29 | ); 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/integration/log_dir.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use App::Yath::Tester qw/yath/; 4 | use File::Temp qw/tempdir/; 5 | 6 | use File::Spec; 7 | 8 | my $dir = __FILE__; 9 | $dir =~ s{\.t$}{}g; 10 | $dir =~ s{^\./}{}; 11 | 12 | my $tmpdir = tempdir(CLEANUP => 1); 13 | 14 | yath( 15 | command => 'test', 16 | args => ["--log-dir=$tmpdir", '-L', '--ext=tx', $dir], 17 | exit => 0, 18 | test => sub { 19 | my $out = shift; 20 | 21 | opendir(my $dh, $tmpdir) or die "Could not open dir $tmpdir: $!"; 22 | my @files; 23 | for my $file (readdir($dh)) { 24 | next if $file =~ m/^\.+$/; 25 | next unless -f File::Spec->catfile($tmpdir, $file); 26 | push @files => $file; 27 | } 28 | 29 | is(@files, 1, "Only 1 file present"); 30 | like($files[0], qr{\.jsonl$}, "File is a jsonl file"); 31 | }, 32 | ); 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/integration/init.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | use Cwd qw/cwd/; 6 | 7 | use App::Yath::Tester qw/yath/; 8 | use App::Yath::Util qw/find_yath/; 9 | find_yath(); # cache result before we chdir 10 | 11 | my $orig = cwd(); 12 | my $dir = tempdir(CLEANUP => 1); 13 | chdir($dir); 14 | 15 | yath( 16 | command => 'init', 17 | args => [], 18 | exit => 0, 19 | test => sub { 20 | like($_, qr/Writing test\.pl/, "Short message"); 21 | 22 | ok(-e 'test.pl', "Added test.pl"); 23 | 24 | open(my $fh, '<', 'test.pl') or die $!; 25 | my $found = 0; 26 | while (my $line = <$fh>) { 27 | next unless $line =~ m/THIS IS A GENERATED YATH RUNNER TEST/; 28 | $found++; 29 | last; 30 | } 31 | 32 | ok($found, "Found generated note"); 33 | }, 34 | ); 35 | 36 | chdir($orig); 37 | 38 | done_testing; 39 | -------------------------------------------------------------------------------- /t/integration/failure_cases/nested_subtest_exception.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::API qw/context/; 3 | 4 | { 5 | $INC{'My/Event.pm'} = 1; 6 | package My::Event; 7 | use parent 'Test2::Event'; 8 | 9 | use Test2::Util::Facets2Legacy ':ALL'; 10 | 11 | sub facet_data { 12 | my $self = shift; 13 | 14 | my $out = $self->common_facet_data; 15 | 16 | $out->{errors} = [ 17 | { tag => 'OOPS', fail => !$ENV{FAILURE_DO_PASS}, details => "An error occured" } 18 | ]; 19 | 20 | return $out; 21 | } 22 | } 23 | 24 | subtest foo => sub { 25 | subtest bar => sub { 26 | subtest baz => sub { 27 | ok(1, "pass"); 28 | 29 | sub { 30 | my $ctx = context; 31 | 32 | $ctx->send_event('+My::Event'); 33 | 34 | $ctx->release; 35 | }->(); 36 | }; 37 | }; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/integration/verbose_env.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use Config qw/%Config/; 4 | use File::Temp qw/tempfile/; 5 | use File::Spec; 6 | 7 | use App::Yath::Tester qw/yath/; 8 | use Test2::Harness::Util::File::JSONL; 9 | 10 | use Test2::Harness::Util qw/clean_path/; 11 | use Test2::Harness::Util::JSON qw/decode_json/; 12 | 13 | my $dir = __FILE__; 14 | $dir =~ s{\.t$}{}g; 15 | $dir =~ s{^\./}{}; 16 | 17 | # Make it very wrong to start 18 | local $ENV{T2_HARNESS_IS_VERBOSE} = 99; 19 | local $ENV{HARNESS_IS_VERBOSE} = 99; 20 | 21 | yath( 22 | command => 'test', 23 | args => [File::Spec->catfile($dir, "not_verbose.tx")], 24 | exit => F(), 25 | ); 26 | 27 | yath( 28 | command => 'test', 29 | args => ['-v', File::Spec->catfile($dir, "verbose1.tx")], 30 | exit => F(), 31 | ); 32 | 33 | yath( 34 | command => 'test', 35 | args => ['-vv', File::Spec->catfile($dir, "verbose2.tx")], 36 | exit => F(), 37 | ); 38 | 39 | done_testing; 40 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Runner/Resource/SharedJobSlots/.sharedjobslots.yml: -------------------------------------------------------------------------------- 1 | --- 2 | COMMON: 3 | state_file: /tmp/yath-state-config-test 4 | algorithm: fair 5 | max_slots: 4 6 | max_slots_per_job: 2 7 | max_slots_per_run: 4 8 | default_slots_per_run: 2 9 | default_slots_per_job: 2 10 | 11 | DEFAULT: 12 | default_slots_per_run: 1 13 | default_slots_per_job: 1 14 | 15 | foo: 16 | max_slots: 13 17 | max_slots_per_job: 5 18 | max_slots_per_run: 13 19 | default_slots_per_run: 3 20 | default_slots_per_job: 2 21 | 22 | bar: 23 | max_slots: 8 24 | max_slots_per_job: 2 25 | max_slots_per_run: 6 26 | default_slots_per_run: 4 27 | default_slots_per_job: 2 28 | state_umask: 0077 29 | 30 | baz: 31 | max_slots: 64 32 | max_slots_per_job: 32 33 | max_slots_per_run: 64 34 | default_slots_per_run: 64 35 | default_slots_per_job: 32 36 | algorithm: first 37 | 38 | bat: 39 | 40 | ban: 41 | use_common: 0 42 | 43 | baf: 44 | max_slots: 7 45 | use_common: 0 46 | -------------------------------------------------------------------------------- /t/integration/coverage/c.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Plugin::Cover; 3 | use Path::Tiny; 4 | 5 | use Test2::Harness::Util::JSON qw/encode_json/; 6 | 7 | Test2::Plugin::Cover->set_from_manager('Manager'); 8 | Test2::Plugin::Cover->set_root(path('t/integration/coverage/lib')->realpath); 9 | 10 | STDIN->blocking(0); 11 | 12 | print "INPUT ${ \__FILE__ }: " . encode_json({ 13 | env => {map { ($_ => $ENV{$_}) } grep { m/^COVER_TEST_/ } keys %ENV}, 14 | argv => [@ARGV], 15 | stdin => join('' => ), 16 | }) . "\n"; 17 | 18 | subtest a => sub { 19 | Test2::Plugin::Cover->set_from({subtest => 'a'}); 20 | require Ax; 21 | is(Ax->a, 'a', "Got a"); 22 | Test2::Plugin::Cover->clear_from(); 23 | }; 24 | 25 | subtest c => sub { 26 | Test2::Plugin::Cover->set_from({subtest => 'c'}); 27 | require Ax; 28 | require Cx; 29 | is(Ax->a, 'a', "Got a"); 30 | is(Cx->c, 'c', "Got c"); 31 | Test2::Plugin::Cover->clear_from(); 32 | }; 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/integration/coverage/lib/Plugin.pm: -------------------------------------------------------------------------------- 1 | package Plugin; 2 | use strict; 3 | use warnings; 4 | 5 | use parent 'App::Yath::Plugin'; 6 | 7 | sub changed_files { 8 | return () unless $ENV{TEST_CASE}; 9 | return (['Ax.pm']) if $ENV{TEST_CASE} eq 'Ax'; 10 | return (['Bx.pm']) if $ENV{TEST_CASE} eq 'Bx'; 11 | return (['Cx.pm']) if $ENV{TEST_CASE} eq 'Cx'; 12 | return (['Bx.pm', 'b']) if $ENV{TEST_CASE} eq 'Bxb'; 13 | return (['Cx.pm', 'c']) if $ENV{TEST_CASE} eq 'Cxc'; 14 | return (['Ax.pm', '*']) if $ENV{TEST_CASE} eq 'Ax*'; 15 | return (['Ax.pm', 'a']) if $ENV{TEST_CASE} eq 'Axa'; 16 | return (['Ax.pm', 'aa']) if $ENV{TEST_CASE} eq 'Axaa'; 17 | return (['Ax.pm', 'aa', 'a']) if $ENV{TEST_CASE} eq 'Axaaa'; 18 | return (['Ax.pm', 'a'], ['Cx.pm', 'c']) if $ENV{TEST_CASE} eq 'AxCx'; 19 | return (); 20 | } 21 | 22 | 1; 23 | -------------------------------------------------------------------------------- /t/integration/includes/not-perl.pl: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use File::Spec; 3 | use Config qw/%Config/; 4 | 5 | my @parts = File::Spec->splitpath(File::Spec->rel2abs(__FILE__)); 6 | pop @parts; 7 | my $path = File::Spec->catpath(@parts); 8 | 9 | use App::Yath; 10 | 11 | like( 12 | \@INC, 13 | [ 14 | App::Yath->app_path, 15 | File::Spec->catdir($path, 'xyz'), 16 | File::Spec->catdir($path, 'lib'), 17 | File::Spec->catdir($path, 'blib', 'lib'), 18 | File::Spec->catdir($path, 'blib', 'arch'), 19 | ], 20 | "Added all the expected paths in order" 21 | ); 22 | 23 | like( 24 | [split $Config{path_sep}, $ENV{PERL5LIB}], 25 | [ 26 | App::Yath->app_path, 27 | File::Spec->catdir($path, 'xyz'), 28 | File::Spec->catdir($path, 'lib'), 29 | File::Spec->catdir($path, 'blib', 'lib'), 30 | File::Spec->catdir($path, 'blib', 'arch'), 31 | ], 32 | "When running non-perl the libs were added via PERL5LIB" 33 | ); 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/integration/times.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use Test2::Harness::Util::File::JSONL; 7 | use App::Yath::Tester qw/yath/; 8 | 9 | my $dir = __FILE__; 10 | $dir =~ s{\.t$}{}g; 11 | $dir =~ s{^\./}{}; 12 | 13 | my $out = yath( 14 | command => 'test', 15 | args => [$dir, '--ext=tx'], 16 | log => 1, 17 | exit => 0, 18 | ); 19 | 20 | my $log = $out->{log}->name; 21 | 22 | yath( 23 | command => 'times', 24 | args => [$log], 25 | exit => 0, 26 | test => sub { 27 | my $out = shift; 28 | 29 | like($out->{output}, qr{Total .* Startup .* Events .* Cleanup .* File}m, "Got header"); 30 | like($out->{output}, qr{t/integration/times/pass\.tx}m, "Got pass line"); 31 | like($out->{output}, qr{t/integration/times/pass2\.tx}m, "Got pass2 line"); 32 | like($out->{output}, qr{TOTAL}m, "Got total line"); 33 | }, 34 | ); 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/do.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::do; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Test2::Harness::Util::File::JSON; 8 | 9 | use Test2::Harness::Util qw/open_file/; 10 | 11 | use parent 'App::Yath::Command'; 12 | use Test2::Harness::Util::HashBase; 13 | 14 | sub group { '' } 15 | 16 | sub summary { "Run tests using 'run' or 'test', same as the default command, but explicit." } 17 | sub cli_args { "[run or test args]" } 18 | 19 | sub description { 20 | return <<" EOT"; 21 | This is the same as running yath without a command, except that it will not 22 | fail on CLI parsing issues that often get mistaken for commands. 23 | 24 | If there is a persistent runner then the 'run' command is used, otherwise the 25 | 'test' command is used. 26 | EOT 27 | } 28 | 29 | sub run { 30 | # This file is actually just a stub for the magic of 'do'. Code is not executed. 31 | die "This should not be reachable"; 32 | } 33 | 34 | 35 | 1; 36 | 37 | __END__ 38 | 39 | =head1 POD IS AUTO-GENERATED 40 | 41 | 42 | -------------------------------------------------------------------------------- /t/unit/Test2/Tools/HarnessTester.t: -------------------------------------------------------------------------------- 1 | use Test2::V0 -target => 'Test2::Tools::HarnessTester'; 2 | use Test2::Tools::HarnessTester qw/summarize_events/; 3 | 4 | imported_ok qw/summarize_events/; 5 | 6 | my $events = intercept { 7 | ok(1, "Pass") for 1 .. 4; 8 | ok(0, "Fail"); 9 | ok(1, "Pass"); 10 | 11 | done_testing; 12 | }; 13 | 14 | is( 15 | summarize_events($events), 16 | { 17 | assertions => 6, 18 | errors => 0, 19 | fail => 1, 20 | failures => 1, 21 | pass => 0, 22 | plan => {count => 6}, 23 | }, 24 | "Failure, assertion count, plan", 25 | ); 26 | 27 | $events = intercept { 28 | ok(1, "Pass") for 1 .. 4; 29 | 30 | done_testing; 31 | }; 32 | 33 | is( 34 | summarize_events($events), 35 | { 36 | assertions => 4, 37 | errors => 0, 38 | fail => 0, 39 | failures => 0, 40 | pass => 1, 41 | plan => {count => 4}, 42 | }, 43 | "pass, assertion count, plan", 44 | ); 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/which.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::which; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use App::Yath::Util qw/find_pfile/; 8 | 9 | use Test2::Harness::Util::File::JSON; 10 | 11 | use parent 'App::Yath::Command'; 12 | use Test2::Harness::Util::HashBase; 13 | 14 | sub group { 'persist' } 15 | 16 | sub summary { "Locate the persistent test runner" } 17 | sub cli_args { "" } 18 | 19 | sub description { 20 | return <<" EOT"; 21 | This will tell you about any persistent runners it can find. 22 | EOT 23 | } 24 | 25 | sub run { 26 | my $self = shift; 27 | 28 | my $pfile = find_pfile($self->settings, no_fatal => 1); 29 | 30 | unless ($pfile) { 31 | print "\nNo persistent harness was found for the current path.\n\n"; 32 | return 0; 33 | } 34 | 35 | print "\nFound: $pfile\n"; 36 | my $data = Test2::Harness::Util::File::JSON->new(name => $pfile)->read(); 37 | print " PID: $data->{pid}\n"; 38 | print " Dir: $data->{dir}\n"; 39 | print "\n"; 40 | 41 | return 0; 42 | } 43 | 44 | 1; 45 | 46 | __END__ 47 | 48 | =head1 POD IS AUTO-GENERATED 49 | 50 | -------------------------------------------------------------------------------- /t/integration/includes.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use IPC::Cmd qw/can_run/; 3 | 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | 8 | use App::Yath::Util qw/find_yath/; 9 | find_yath(); # cache result before we chdir 10 | 11 | my $dir = __FILE__; 12 | $dir =~ s{\.t$}{}g; 13 | $dir =~ s{^\./}{}; 14 | 15 | chdir($dir); 16 | $ENV{OLD_PERL5LIB} = $ENV{PERL5LIB}; 17 | 18 | yath( 19 | command => 'test', 20 | args => ['default.tx'], 21 | exit => 0, 22 | ); 23 | 24 | yath( 25 | command => 'test', 26 | args => ['-Ixyz', 'default-i.tx'], 27 | exit => 0, 28 | ); 29 | 30 | yath( 31 | command => 'test', 32 | args => ['-Ia', '-b', '-Ib', '-l', '-Ic', 'order-ibili.tx'], 33 | exit => 0, 34 | ); 35 | 36 | yath( 37 | command => 'test', 38 | args => ['-Ia', '-l', '-Ib', '-b', '-Ic', 'order-ilibi.tx'], 39 | exit => 0, 40 | ); 41 | 42 | yath( 43 | command => 'test', 44 | args => ['-Ixyz', '--unsafe-inc', 'dot-last.tx'], 45 | exit => 0, 46 | ); 47 | 48 | $ENV{YATH_PERL} = $^X; 49 | yath( 50 | command => 'test', 51 | args => ['-Ixyz', './not-perl.sh'], 52 | exit => 0, 53 | ) if can_run('bash'); 54 | 55 | done_testing; 56 | -------------------------------------------------------------------------------- /t/integration/failed.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | use Test2::Harness::Util::File::JSONL; 8 | 9 | use Test2::Harness::Util::JSON qw/decode_json/; 10 | 11 | my $dir = __FILE__; 12 | $dir =~ s{\.t$}{}g; 13 | $dir =~ s{^\./}{}; 14 | 15 | yath( 16 | command => 'test', 17 | args => [$dir, '--ext=tx'], 18 | log => 1, 19 | exit => T(), 20 | test => sub { 21 | my $out = shift; 22 | my $logfile = $out->{log}->name; 23 | 24 | $out = yath( 25 | command => 'failed', 26 | args => [$logfile], 27 | env => {TABLE_TERM_SIZE => 1000, TS_TERM_SIZE => 1000}, 28 | exit => 0, 29 | test => sub { 30 | my $out = shift; 31 | 32 | ok(!$out->{exit}, "'failed' command exits true"); 33 | like($out->{output}, qr{fail\.tx}, "'fail.tx' was seen as a failure when reading the log"); 34 | unlike($out->{output}, qr{pass\.tx}, "'pass.tx' was not seen as a failure when reading the log"); 35 | }, 36 | ); 37 | }, 38 | ); 39 | 40 | 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/integration/coverage/a.tx: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Plugin::Cover; 3 | use Path::Tiny; 4 | 5 | use Test2::Harness::Util::JSON qw/encode_json/; 6 | 7 | STDIN->blocking(0); 8 | 9 | print "INPUT ${ \__FILE__ }: " . encode_json({ 10 | env => {map { ($_ => $ENV{$_}) } grep { m/^COVER_TEST_/ } keys %ENV}, 11 | argv => [@ARGV], 12 | stdin => join('' => ), 13 | }) . "\n"; 14 | 15 | use Ax; 16 | use Bx; 17 | use Cx; 18 | 19 | Test2::Plugin::Cover->set_from_manager('Manager'); 20 | Test2::Plugin::Cover->set_root(path('t/integration/coverage/lib')->realpath); 21 | 22 | is(Cx->c, 'c', "Got c"); 23 | 24 | subtest a => sub { 25 | Test2::Plugin::Cover->set_from({subtest => 'a'}); 26 | is(Ax->a, 'a', "Got a"); 27 | is(Ax->aa, 'aa', "Got aa"); 28 | Test2::Plugin::Cover->clear_from(); 29 | }; 30 | 31 | subtest b => sub { 32 | Test2::Plugin::Cover->set_from({subtest => 'b'}); 33 | is(Ax->a, 'a', "Got a"); 34 | is(Bx->b, 'b', "Got b"); 35 | Test2::Plugin::Cover->clear_from(); 36 | }; 37 | 38 | subtest c => sub { 39 | Test2::Plugin::Cover->set_from({subtest => 'c'}); 40 | is(Ax->a, 'a', "Got a"); 41 | is(Bx->b, 'b', "Got b"); 42 | is(Cx->c, 'c', "Got c"); 43 | Test2::Plugin::Cover->clear_from(); 44 | }; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/integration/failure_cases.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | # HARNESS-DURATION-LONG 3 | 4 | use Test2::API qw/context/; 5 | use App::Yath::Tester qw/yath/; 6 | 7 | my $dir = __FILE__; 8 | $dir =~ s{\.t$}{}g; 9 | $dir =~ s{^\./}{}; 10 | 11 | my %CUSTOM = ( 12 | "timeout.tx" => ['--et', 2], 13 | "post_exit_timeout.tx" => ['--pet', 2], 14 | "noplan.tx" => ['--pet', 2], 15 | "dupnums.tx" => [], 16 | "missingnums.tx" => [], 17 | ); 18 | 19 | opendir(my $DH, $dir) or die "Could not open directory $dir: $!"; 20 | 21 | for my $file (readdir($DH)) { 22 | run_test($file); 23 | } 24 | 25 | sub run_test { 26 | my ($file) = @_; 27 | my $path = File::Spec->canonpath("$dir/$file"); 28 | return unless -f $path; 29 | my $args = $CUSTOM{$file}; 30 | 31 | my $ctx = context(); 32 | 33 | my @final_args = (@{$args || []}, $path); 34 | 35 | yath( 36 | command => 'test', 37 | args => \@final_args, 38 | env => {FAILURE_DO_PASS => 0}, 39 | exit => T(), 40 | ); 41 | 42 | yath( 43 | command => 'test', 44 | args => \@final_args, 45 | env => {FAILURE_DO_PASS => 1}, 46 | exit => F(), 47 | ); 48 | 49 | $ctx->release; 50 | } 51 | 52 | done_testing; 53 | -------------------------------------------------------------------------------- /t2/tmp_perms.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use File::Spec; 3 | use Test2::Harness::Util qw/clean_path/; 4 | use Fcntl ':mode'; 5 | 6 | sub check_perms { 7 | my $file = shift; 8 | my $mode = (stat($file))[2]; 9 | 10 | my @bad; 11 | $mode & S_ISVTX or push @bad => "$file does not have sticky-bit"; 12 | $mode & S_IRWXU or push @bad => "$file is not user RWX"; 13 | $mode & S_IRWXG or push @bad => "$file is not group RWX"; 14 | $mode & S_IRWXO or push @bad => "$file is not other RWX"; 15 | 16 | return \@bad; 17 | } 18 | 19 | my $system_tmp = clean_path($ENV{SYSTEM_TMPDIR}); 20 | 21 | my $problems = check_perms($system_tmp); 22 | skip_all join ", " => @$problems if @$problems; 23 | 24 | my $path = $ENV{TMPDIR}; 25 | is(check_perms($path), [], "tempdir has correct permissions"); 26 | 27 | my $last = $path; 28 | my $cnt = 0; 29 | while ($system_tmp) { 30 | my $next = clean_path(File::Spec->catdir($last, File::Spec->updir())); 31 | last if $next eq $system_tmp; # We hit system temp, we can stop 32 | last if $next eq $last; # We probably hit root 33 | last if $cnt++ > 10; # Something went wrong, no need to loop forever 34 | $last = $next; 35 | 36 | is(check_perms($next), [], "$next has correct permissions"); 37 | } 38 | 39 | done_testing; 40 | -------------------------------------------------------------------------------- /lib/Test2/Harness.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | 1; 8 | 9 | __END__ 10 | 11 | =pod 12 | 13 | =encoding UTF-8 14 | 15 | =head1 NAME 16 | 17 | Test2::Harness - A new and improved test harness with better L 18 | integration. 19 | 20 | =head1 DESCRIPTION 21 | 22 | Test2::Harness is the backend code that handles running/processing the tests. 23 | In general a user will not use it directly, instead you should probably be 24 | looking at L which is the UI layer built around Test2::Harness. 25 | 26 | =head1 SEE ALSO 27 | 28 | The primary documentation can be found in L. 29 | 30 | =head1 SOURCE 31 | 32 | The source code repository for Test2-Harness can be found at 33 | F. 34 | 35 | =head1 MAINTAINERS 36 | 37 | =over 4 38 | 39 | =item Chad Granum Eexodist@cpan.orgE 40 | 41 | =back 42 | 43 | =head1 AUTHORS 44 | 45 | =over 4 46 | 47 | =item Chad Granum Eexodist@cpan.orgE 48 | 49 | =back 50 | 51 | =head1 COPYRIGHT 52 | 53 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 54 | 55 | This program is free software; you can redistribute it and/or 56 | modify it under the same terms as Perl itself. 57 | 58 | See F 59 | 60 | =cut 61 | -------------------------------------------------------------------------------- /t/unit/App/Yath/Command/init.t: -------------------------------------------------------------------------------- 1 | use Test2::V0 -target => 'App::Yath::Command::init'; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok $CLASS; 5 | 6 | use App::Yath::Tester qw/make_example_dir/; 7 | 8 | use Cwd qw/getcwd/; 9 | my $orig = getcwd(); 10 | 11 | subtest run => sub { 12 | my $dir = make_example_dir(); 13 | chdir($dir); 14 | 15 | unlink('test.pl') or die "Could not unlink test.pl" 16 | if -e 'test.pl'; 17 | 18 | my $stdout = ""; 19 | { 20 | local *STDOUT; 21 | open(STDOUT, '>', \$stdout); 22 | is($CLASS->run(), 0, "Exit of 0"); 23 | ok(-e 'test.pl', "Added test.pl"); 24 | 25 | is($CLASS->run(), 0, "Exit of 0 if we are updating a generated one"); 26 | 27 | unlink('test.pl') or die "Could not unlink test.pl"; 28 | 29 | open(my $fh, '>', 'test.pl') or die "Could not open test.pl"; 30 | print $fh "xx\n"; 31 | close($fh); 32 | } 33 | 34 | is( 35 | $stdout, 36 | "\nWriting test.pl...\n\n\nWriting test.pl...\n\n", 37 | "Saw write info both times" 38 | ); 39 | 40 | is( 41 | dies { $CLASS->run() }, 42 | "'test.pl' already exists, and does not appear to be a yath runner.\n", 43 | "Cannot override a non-generated test.pl" 44 | ); 45 | }; 46 | 47 | done_testing; 48 | chdir($orig); 49 | -------------------------------------------------------------------------------- /t/integration/reload/lib/Preload.pm: -------------------------------------------------------------------------------- 1 | package Preload; 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::Harness::Runner::Preload; 6 | 7 | print "$$ $0 - Loaded ${ \__PACKAGE__ }\n"; 8 | 9 | my $path = __FILE__; 10 | $path =~ s{\.pm$}{}; 11 | use Data::Dumper; 12 | print Dumper($path); 13 | 14 | stage A => sub { 15 | default(); 16 | 17 | watch "$path/nonperl1" => sub { print "$$ $0 - RELOAD CALLBACK nonperl1\n" }; 18 | 19 | preload sub { 20 | watch "$path/nonperl2" => sub { print "$$ $0 - RELOAD CALLBACK nonperl2\n" }; 21 | }; 22 | 23 | preload 'Preload::A'; 24 | preload 'Preload::WarningA'; 25 | preload 'Preload::ExceptionA'; 26 | preload 'Preload::ExporterA'; 27 | preload 'Preload::Churn'; 28 | }; 29 | 30 | stage B => sub { 31 | reload_remove_check sub { 32 | my %params = @_; 33 | return 1 if $params{reload_file} eq $params{from_file}; 34 | return 0; 35 | }; 36 | 37 | preload sub { 38 | *Preload::B::PreDefined = sub { 'yes' }; 39 | }; 40 | 41 | preload 'Preload::A'; 42 | preload 'Preload::WarningA'; 43 | preload 'Preload::ExceptionA'; 44 | preload 'Preload::ExporterA'; 45 | 46 | preload 'Preload::B'; 47 | preload 'Preload::WarningB'; 48 | preload 'Preload::ExceptionB'; 49 | preload 'Preload::ExporterB'; 50 | 51 | preload 'Preload::IncChange'; 52 | }; 53 | 54 | 1; 55 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/kill.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::kill; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Time::HiRes qw/sleep/; 8 | use App::Yath::Util qw/find_pfile/; 9 | use File::Path qw/remove_tree/; 10 | 11 | use Test2::Harness::Util::File::JSON(); 12 | 13 | use parent 'App::Yath::Command::abort'; 14 | use Test2::Harness::Util::HashBase; 15 | 16 | sub group { 'persist' } 17 | 18 | sub summary { "Kill the runner and any running or pending tests" } 19 | sub cli_args { "" } 20 | 21 | sub description { 22 | return <<" EOT"; 23 | This command will kill the active yath runner and any running or pending tests. 24 | EOT 25 | } 26 | 27 | sub pfile_params { (no_checks => 1) } 28 | 29 | sub run { 30 | my $self = shift; 31 | 32 | my $data = $self->pfile_data(); 33 | my $pfile = $data->{pfile_path}; 34 | 35 | $self->App::Yath::Command::test::terminate_queue(); 36 | 37 | $_->teardown($self->settings) for @{$self->settings->harness->plugins}; 38 | 39 | $self->SUPER::run(); 40 | 41 | sleep(0.02) while kill(0, $self->pfile_data->{pid}); 42 | unlink($pfile) if -f $pfile; 43 | remove_tree($self->workdir, {safe => 1, keep_root => 0}) if -d $self->workdir; 44 | print "\n\nRunner stopped\n\n" unless $self->settings->display->quiet; 45 | 46 | return 0; 47 | } 48 | 49 | 1; 50 | 51 | __END__ 52 | 53 | =head1 POD IS AUTO-GENERATED 54 | 55 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/stop.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::stop; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Time::HiRes qw/sleep/; 8 | 9 | use File::Spec(); 10 | 11 | use Test2::Harness::Util::File::JSON(); 12 | use Test2::Harness::Util::Queue(); 13 | 14 | use Test2::Harness::Util qw/open_file/; 15 | use App::Yath::Util qw/find_pfile/; 16 | use File::Path qw/remove_tree/; 17 | 18 | use parent 'App::Yath::Command::run'; 19 | use Test2::Harness::Util::HashBase; 20 | 21 | sub group { 'persist' } 22 | 23 | sub summary { "Stop the persistent test runner" } 24 | sub cli_args { "" } 25 | 26 | sub description { 27 | return <<" EOT"; 28 | This command will stop a persistent instance, and output any log contents. 29 | EOT 30 | } 31 | 32 | sub pfile_params { (no_fatal => 1) } 33 | 34 | sub run { 35 | my $self = shift; 36 | 37 | $self->App::Yath::Command::test::terminate_queue(); 38 | 39 | $_->teardown($self->settings) for @{$self->settings->harness->plugins}; 40 | 41 | sleep(0.02) while kill(0, $self->pfile_data->{pid}); 42 | 43 | my $pfile = $self->pfile; 44 | unlink($pfile) if -f $pfile; 45 | 46 | remove_tree($self->workdir, {safe => 1, keep_root => 0}) if -d $self->workdir; 47 | 48 | print "\n\nRunner stopped\n\n" unless $self->settings->display->quiet; 49 | return 0; 50 | } 51 | 52 | 1; 53 | 54 | __END__ 55 | 56 | =head1 POD IS AUTO-GENERATED 57 | 58 | -------------------------------------------------------------------------------- /t2/utf8-2.t: -------------------------------------------------------------------------------- 1 | use utf8; 2 | use strict; 3 | use warnings; 4 | use Test::More; 5 | use Test2::Plugin::UTF8; 6 | use Test2::API qw/test2_stack/; 7 | use Test2::Harness::Util::JSON qw/decode_json/; 8 | use Test2::Tools::Basic qw/skip_all/; 9 | use File::Spec; 10 | use Test2::Util qw/get_tid ipc_separator/; 11 | # HARNESS-DURATION-SHORT 12 | # HARNESS-NO-IO-EVENTS 13 | 14 | test2_stack()->top; 15 | my ($hub) = test2_stack()->all(); 16 | my $fmt = $hub->format; 17 | skip_all "This test requires the stream formatter" 18 | unless $fmt && $fmt->isa('Test2::Formatter::Stream'); 19 | 20 | print STDOUT "STDOUT: Mākaha\n"; 21 | note "NOTE: Mākaha"; 22 | ok(1, "ASSERT: Mākaha"); 23 | 24 | my $file = File::Spec->catfile($fmt->dir, join(ipc_separator() => 'events', $$, 0) . ".jsonl"); 25 | open(my $events_fh, '<:utf8', $file) or die "Could not open events file: $!"; 26 | open(my $stdout_fh, '<:utf8', File::Spec->catfile($ENV{TEST2_JOB_DIR}, 'stdout')) or die "Could not open STDOUT for reading: $!"; 27 | 28 | my @events = map { decode_json($_) } grep m/(NOTE|DIAG|ASSERT): /, <$events_fh>; 29 | my ($stdout) = grep m/STDOUT: /, <$stdout_fh>; 30 | 31 | is($stdout, "STDOUT: Mākaha\n", "Round trip STDOUT encoding/decoding"); 32 | 33 | is($events[0]->{facet_data}->{info}->[0]->{details}, "NOTE: Mākaha", "Round trip encoding/decoding a note"); 34 | is($events[1]->{facet_data}->{assert}->{details}, "ASSERT: Mākaha", "Round trip encoding/decoding an assert"); 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/reload.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::reload; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use File::Spec(); 8 | use Test2::Harness::Util::File::JSON; 9 | 10 | use App::Yath::Util qw/find_pfile/; 11 | use Test2::Harness::Util qw/open_file/; 12 | 13 | use parent 'App::Yath::Command'; 14 | use Test2::Harness::Util::HashBase; 15 | 16 | sub group { 'persist' } 17 | 18 | sub summary { "Reload the persistent test runner" } 19 | sub cli_args { "" } 20 | 21 | sub description { 22 | return <<" EOT"; 23 | This will send a SIGHUP to the persistent runner, forcing it to reload. This 24 | will also clear the blacklist allowing all preloads to load as normal. 25 | EOT 26 | } 27 | 28 | sub run { 29 | my $self = shift; 30 | 31 | my $pfile = find_pfile($self->settings, no_fatal => 1) 32 | or die "Could not find a persistent yath running.\n"; 33 | 34 | my $data = Test2::Harness::Util::File::JSON->new(name => $pfile)->read(); 35 | 36 | my $blacklist = File::Spec->catfile($data->{dir}, 'BLACKLIST'); 37 | if (-e $blacklist) { 38 | print "Deleting module blacklist...\n"; 39 | unlink($blacklist) or warn "Could not delete blacklist file!"; 40 | } 41 | 42 | print "\nSending SIGHUP to $data->{pid}\n\n"; 43 | kill('HUP', $data->{pid}) or die "Could not send signal!\n"; 44 | return 0; 45 | } 46 | 47 | 1; 48 | 49 | __END__ 50 | 51 | =head1 POD IS AUTO-GENERATED 52 | 53 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | --indent-columns=4 # size of indentation 2 | --nt # no tabs 3 | --continuation-indentation=4 # indentation of wrapped lines 4 | --maximum-line-length=0 # max line length before wrapping (turn it off) 5 | --nooutdent-long-quotes # do not outdent overly long quotes 6 | --paren-tightness=2 # no spacing for parentheses 7 | --square-bracket-tightness=2 # no spacing for square brackets 8 | --brace-tightness=2 # no spacing for hash curly braces 9 | --block-brace-tightness=0 # spacing for coderef curly braces 10 | --comma-arrow-breakpoints=1 # break long key/value pair lists 11 | --break-at-old-comma-breakpoints # this attempts to retain list break points 12 | --no-blanks-before-comments # do not insert blank lines before comments 13 | --indent-spaced-block-comments # no blanks before comments 14 | --nocuddled-else # Do not cuddle else 15 | --nospace-for-semicolon # no space before semicolons in loops 16 | --nospace-terminal-semicolon # no space before termonal semicolons 17 | --notrim-qw # Do not mess with qw{} whitespace 18 | --opening-token-right # make "push @array, {" indent nicely 19 | --converge # Run until the output stops changing 20 | --extended-syntax # For Fennec/Test2 and other Devel::Declare syntax tools 21 | --no-outdent-labels # Do not outdent labels 22 | --wn 23 | -------------------------------------------------------------------------------- /lib/App/Yath/Options/Persist.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Options::Persist; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Test2::Util qw/IS_WIN32/; 8 | use Test2::Harness::Util qw/clean_path/; 9 | 10 | use App::Yath::Options; 11 | 12 | option_group {prefix => 'runner', category => "Runner Options"} => sub { 13 | option daemon => ( 14 | description => 'Start the runner as a daemon (Default: True)', 15 | default => 1, 16 | ); 17 | }; 18 | 19 | 1; 20 | 21 | __END__ 22 | 23 | 24 | =pod 25 | 26 | =encoding UTF-8 27 | 28 | =head1 NAME 29 | 30 | App::Yath::Options::Persist - Persistent Runner options for Yath. 31 | 32 | =head1 DESCRIPTION 33 | 34 | This is where the command line options for the persistent runner are defined. 35 | 36 | =head1 PROVIDED OPTIONS POD IS AUTO-GENERATED 37 | 38 | =head1 SOURCE 39 | 40 | The source code repository for Test2-Harness can be found at 41 | F. 42 | 43 | =head1 MAINTAINERS 44 | 45 | =over 4 46 | 47 | =item Chad Granum Eexodist@cpan.orgE 48 | 49 | =back 50 | 51 | =head1 AUTHORS 52 | 53 | =over 4 54 | 55 | =item Chad Granum Eexodist@cpan.orgE 56 | 57 | =back 58 | 59 | =head1 COPYRIGHT 60 | 61 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 62 | 63 | This program is free software; you can redistribute it and/or 64 | modify it under the same terms as Perl itself. 65 | 66 | See F 67 | 68 | =cut 69 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/auditor.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::auditor; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use File::Spec; 8 | use Scalar::Util qw/blessed/; 9 | 10 | use App::Yath::Util qw/isolate_stdout/; 11 | 12 | use Test2::Harness::Util::JSON qw/decode_json encode_json/; 13 | use Test2::Harness::Util qw/mod2file/; 14 | 15 | use Test2::Harness::Run; 16 | 17 | use parent 'App::Yath::Command'; 18 | use Test2::Harness::Util::HashBase; 19 | 20 | sub internal_only { 1 } 21 | sub summary { "For internal use only" } 22 | sub name { 'auditor' } 23 | 24 | sub run { 25 | my $self = shift; 26 | my ($auditor_class, $run_id, %args) = @{$self->{+ARGS}}; 27 | 28 | my $name = 'yath-auditor'; 29 | $name = "$args{procname_prefix}-${name}" if $args{procname_prefix}; 30 | $0 = $name; 31 | 32 | my $fh = isolate_stdout(); 33 | 34 | require(mod2file($auditor_class)); 35 | 36 | my $auditor = $auditor_class->new( 37 | %args, 38 | run_id => $run_id, 39 | action => sub { print $fh defined($_[0]) ? blessed($_[0]) ? $_[0]->as_json . "\n" : encode_json($_[0]) . "\n" : "null\n" }, 40 | ); 41 | 42 | local $SIG{PIPE} = 'IGNORE'; 43 | my $ok = eval { $auditor->process(); 1 }; 44 | my $err = $@; 45 | 46 | eval { $auditor->finish(); 1 } or warn $@; 47 | 48 | die $err unless $ok; 49 | 50 | return 0; 51 | } 52 | 53 | 1; 54 | 55 | __END__ 56 | 57 | =head1 POD IS AUTO-GENERATED 58 | 59 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Runner/Preloader/Stage.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Runner::Preloader::Stage; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use parent 'Test2::Harness::IPC::Process'; 8 | use Test2::Harness::Util::HashBase qw{ {+CATEGORY} //= 'stage' } 11 | 12 | 1; 13 | 14 | __END__ 15 | 16 | 17 | =pod 18 | 19 | =encoding UTF-8 20 | 21 | =head1 NAME 22 | 23 | Test2::Harness::Runner::Preloader::Stage - Representation of a persistent stage process. 24 | 25 | =head1 DESCRIPTION 26 | 27 | This module is responsible for preloading libraries for a specific stage before 28 | running tests. This entire module is considered an "Implementation Detail". 29 | Please do not rely on it always staying the same, or even existing in the 30 | future. Do not use this directly. 31 | 32 | =head1 SOURCE 33 | 34 | The source code repository for Test2-Harness can be found at 35 | F. 36 | 37 | =head1 MAINTAINERS 38 | 39 | =over 4 40 | 41 | =item Chad Granum Eexodist@cpan.orgE 42 | 43 | =back 44 | 45 | =head1 AUTHORS 46 | 47 | =over 4 48 | 49 | =item Chad Granum Eexodist@cpan.orgE 50 | 51 | =back 52 | 53 | =head1 COPYRIGHT 54 | 55 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 56 | 57 | This program is free software; you can redistribute it and/or 58 | modify it under the same terms as Perl itself. 59 | 60 | See F 61 | 62 | =cut 63 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Runner/Constants.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Runner::Constants; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Importer Importer => 'import'; 8 | 9 | our @EXPORT = qw/CATEGORIES DURATIONS/; 10 | 11 | use constant CATEGORIES => {general => 1, isolation => 1, immiscible => 1}; 12 | use constant DURATIONS => {long => 1, medium => 1, short => 1}; 13 | 14 | 1; 15 | 16 | __END__ 17 | 18 | =pod 19 | 20 | =encoding UTF-8 21 | 22 | =head1 NAME 23 | 24 | Test2::Harness::Runner::Constants - Constants shared between multiple runner 25 | modules. 26 | 27 | =head1 DESCRIPTION 28 | 29 | Export some common structures. 30 | 31 | =head1 SYNOPSIS 32 | 33 | use Test2::Harness::Runner::Constants qw/CATEGORIES DURATIONS/; 34 | 35 | if (CATEGORIES->{$cat}) { 36 | print "$cat is valid\n"; 37 | } 38 | else { 39 | print "$cat is not valid\n"; 40 | } 41 | 42 | =head1 SOURCE 43 | 44 | The source code repository for Test2-Harness can be found at 45 | F. 46 | 47 | =head1 MAINTAINERS 48 | 49 | =over 4 50 | 51 | =item Chad Granum Eexodist@cpan.orgE 52 | 53 | =back 54 | 55 | =head1 AUTHORS 56 | 57 | =over 4 58 | 59 | =item Chad Granum Eexodist@cpan.orgE 60 | 61 | =back 62 | 63 | =head1 COPYRIGHT 64 | 65 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 66 | 67 | This program is free software; you can redistribute it and/or 68 | modify it under the same terms as Perl itself. 69 | 70 | See F 71 | 72 | =cut 73 | -------------------------------------------------------------------------------- /t/integration/speedtag.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | use File::Copy qw/copy/; 6 | 7 | use Test2::Harness::Util::File::JSONL; 8 | 9 | use App::Yath::Tester qw/yath/; 10 | 11 | use App::Yath::Util qw/find_yath/; 12 | find_yath(); # cache result before we chdir 13 | 14 | my $tmp = tempdir(CLEANUP => 1); 15 | 16 | my $dir = __FILE__; 17 | $dir =~ s{\.t$}{}g; 18 | $dir =~ s{^\./}{}; 19 | 20 | my $pass = File::Spec->catfile($tmp, 'pass.tx'); 21 | my $pass2 = File::Spec->catfile($tmp, 'pass2.tx'); 22 | 23 | copy(File::Spec->catfile($dir, 'pass.tx'), $pass); 24 | copy(File::Spec->catfile($dir, 'pass2.tx'), $pass2); 25 | 26 | my $out = yath(command => 'test', args => [$tmp, '--ext=tx'], log => 1, exit => 0); 27 | my $log = $out->{log}->name; 28 | 29 | yath( 30 | command => 'speedtag', 31 | args => [$log], 32 | exit => 0, 33 | test => sub { 34 | like($_, qr/Tagged .*pass\.tx/, "Indicate we tagged pass"); 35 | like($_, qr/Tagged .*pass2\.tx/, "Indicate we tagged pass2"); 36 | 37 | for my $file ($pass, $pass2) { 38 | open(my $fh, '<', $file) or die $!; 39 | my $found = 0; 40 | while (my $line = <$fh>) { 41 | chomp($line); 42 | next unless $line =~ m/^#\s*HARNESS-DURATION-(SHORT|MEDIUM|LONG)$/; 43 | $found = 1; 44 | last; 45 | } 46 | $file =~ s/^.*(pass\d?\.tx)$/$1/; 47 | ok($found, "Tagged file $file"); 48 | } 49 | }, 50 | ); 51 | 52 | done_testing; 53 | -------------------------------------------------------------------------------- /t/1-pod_name.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use Test2::Harness::Util qw/file2mod/; 4 | use File::Find; 5 | 6 | my @files; 7 | find(\&wanted, 'lib/'); 8 | 9 | sub wanted { 10 | my $file = $File::Find::name; 11 | return unless $file =~ m/\.pm$/; 12 | 13 | my $mod = $file; 14 | $mod =~ s{^.*lib/}{}g; 15 | $mod = file2mod($mod); 16 | 17 | push @files => [$file, $mod]; 18 | }; 19 | 20 | my %bad_files; 21 | for my $set (@files) { 22 | my ($file, $mod) = @$set; 23 | 24 | my @res; 25 | 26 | open(my $fh, '<', "$file") or die "Could not open file '$file': $!"; 27 | 28 | chomp(my $start = <$fh>); 29 | push @res => is($start, "package $mod;", "$file has correct package $mod", "Incorrect: $start"); 30 | 31 | my $found; 32 | while(my $line = <$fh>) { 33 | chomp($line); 34 | if ($line eq "=head1 POD IS AUTO-GENERATED") { 35 | $found = 1; 36 | last; 37 | } 38 | next unless $line eq '=head1 NAME'; 39 | 40 | $found = 1; 41 | 42 | my $space = <$fh> // last; 43 | chomp(my $check = <$fh> // ''); 44 | push @res => like($check, qr/^\Q$mod - \E.+$/, "$file POD has correct package '$mod' under NAME"); 45 | 46 | last; 47 | } 48 | 49 | push @res => ok($found, "Found 'NAME' section in $file POD"); 50 | 51 | next unless grep { !$_ } @res; 52 | $bad_files{$file} = $mod; 53 | }; 54 | 55 | if (keys %bad_files) { 56 | my $diag = "All files with errors:\n"; 57 | for my $file (sort keys %bad_files) { 58 | $diag .= "$file\n"; 59 | } 60 | 61 | diag $diag; 62 | } 63 | 64 | done_testing; 65 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Util/UUID.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Util::UUID; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Data::UUID; 8 | use Importer 'Importer' => 'import'; 9 | 10 | our @EXPORT = qw/gen_uuid/; 11 | our @EXPORT_OK = qw/UG gen_uuid/; 12 | 13 | my ($UG, $UG_PID); 14 | sub UG { 15 | return $UG if $UG && $UG_PID && $UG_PID == $$; 16 | 17 | $UG_PID = $$; 18 | return $UG = Data::UUID->new; 19 | } 20 | 21 | sub gen_uuid { UG()->create_str() } 22 | 23 | 1; 24 | 25 | __END__ 26 | 27 | =pod 28 | 29 | =encoding UTF-8 30 | 31 | =head1 NAME 32 | 33 | Test2::Harness::Util::UUID - Utils for generating UUIDs. 34 | 35 | =head1 DESCRIPTION 36 | 37 | This module provides a consistent UUID source for all of Test2::Harness. 38 | 39 | =head1 SYNOPSIS 40 | 41 | use Test2::Harness::Util::UUID qw/gen_uuid/; 42 | 43 | my $uuid = gen_uuid; 44 | 45 | =head1 EXPORTS 46 | 47 | =over 4 48 | 49 | =item $uuid = gen_uuid() 50 | 51 | Generate a UUID. 52 | 53 | =back 54 | 55 | =head1 SOURCE 56 | 57 | The source code repository for Test2-Harness can be found at 58 | F. 59 | 60 | =head1 MAINTAINERS 61 | 62 | =over 4 63 | 64 | =item Chad Granum Eexodist@cpan.orgE 65 | 66 | =back 67 | 68 | =head1 AUTHORS 69 | 70 | =over 4 71 | 72 | =item Chad Granum Eexodist@cpan.orgE 73 | 74 | =back 75 | 76 | =head1 COPYRIGHT 77 | 78 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 79 | 80 | This program is free software; you can redistribute it and/or 81 | modify it under the same terms as Perl itself. 82 | 83 | See F 84 | 85 | =cut 86 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Settings/Prefix.t: -------------------------------------------------------------------------------- 1 | use Test2::V0 -target => 'Test2::Harness::Settings::Prefix'; 2 | 3 | my $one = $CLASS->new(); 4 | isa_ok($one, [$CLASS], "Created an instance"); 5 | ref_ok($one, 'REF', "Hash is slightly obscured by an extra deref"); 6 | 7 | like( 8 | dies { $one->foo }, 9 | qr/The 'foo' field does not exist/, 10 | "Must use a valid field" 11 | ); 12 | 13 | ref_ok($one->vivify_field('foo'), 'SCALAR', "vivify returns a ref"); 14 | is($one->foo, undef, "Not set yet"); 15 | 16 | $one->foo('bar'); 17 | is($one->foo, 'bar', "Set value"); 18 | 19 | if ("$]" >= 5.016) { 20 | $one->foo = 'baz'; 21 | is($one->foo, 'baz', "Set via lvalue"); 22 | } 23 | else { 24 | $one->field(foo => 'baz'); 25 | } 26 | 27 | is($one->field('foo'), 'baz', "Got via field"); 28 | $one->field('foo', 'xxx'); 29 | is($one->field('foo'), 'xxx', "Set via field"); 30 | 31 | like( 32 | dies { $one->field('foo', 'bar', 'baz') }, 33 | qr/Too many arguments for field\(\)/, 34 | "Field only takes 2 args" 35 | ); 36 | 37 | like( 38 | dies { $CLASS->foo }, 39 | qr/Method foo\(\) must be called on a blessed instance/, 40 | "Autload does not work on class" 41 | ); 42 | 43 | is( 44 | $one->TO_JSON, 45 | { foo => 'xxx' }, 46 | "JSON structure" 47 | ); 48 | 49 | { 50 | $INC{'TheThing.pm'} = 1; 51 | package TheThing; 52 | use Test2::Harness::Util::HashBase qw/foo bar/; 53 | } 54 | 55 | my $res = $one->build('TheThing', bar => 'yyy'); 56 | isa_ok($res, ['TheThing'], "Created an instance"); 57 | is( 58 | $res, 59 | { 60 | foo => 'xxx', 61 | bar => 'yyy', 62 | }, 63 | "Created with args" 64 | ); 65 | 66 | done_testing; 67 | -------------------------------------------------------------------------------- /t/integration/preload/lib/TestPreload.pm: -------------------------------------------------------------------------------- 1 | package TestPreload; 2 | use strict; 3 | use warnings; 4 | use Time::HiRes qw/sleep time/; 5 | use File::Temp qw/tempdir/; 6 | use File::Spec; 7 | 8 | use Test2::Harness::Runner::Preload; 9 | 10 | my $dir = tempdir(CLEANUP => 1); 11 | my $TRIGGER = File::Spec->catfile($dir, 'trigger'); 12 | 13 | file_stage sub { 14 | my ($file) = @_; 15 | 16 | return uc($1) if $file =~ m/(AAA|BBB)\.tx$/i; 17 | 18 | return; 19 | }; 20 | 21 | stage AAA => sub { 22 | preload 'AAA'; 23 | 24 | stage BBB => sub { 25 | preload 'BBB'; 26 | }; 27 | }; 28 | 29 | our %HOOKS; 30 | stage CCC => sub { 31 | $HOOKS{INIT} = [time(), $$]; 32 | pre_fork sub { $HOOKS{PRE_FORK} = [time(), $$] }; 33 | post_fork sub { $HOOKS{POST_FORK} = [time(), $$] }; 34 | pre_launch sub { $HOOKS{PRE_LAUNCH} = [time(), $$] }; 35 | 36 | preload 'CCC'; 37 | }; 38 | 39 | stage FAST => sub { 40 | eager; 41 | default; 42 | 43 | preload 'FAST'; 44 | 45 | preload sub { 46 | eval <<" EOT" or die $@; 47 | #line ${ \__LINE__ } "${ \__FILE__ }" 48 | END { 49 | return unless \$0 =~ m/slow\.tx/; 50 | open(my \$fh, '>', "$TRIGGER") or die "XXX"; 51 | print \$fh "\n"; 52 | close(\$fh); 53 | } 54 | 1; 55 | EOT 56 | }; 57 | 58 | stage SLOW => sub { 59 | preload sub { 60 | print "$0 pending...\n"; 61 | use Carp qw/cluck/; 62 | local $SIG{ALRM} = sub { cluck "oops"; exit 255 }; 63 | alarm 5; 64 | until (-f $TRIGGER) { 65 | print "$0 Waiting...\n"; 66 | sleep 0.2 67 | } 68 | }; 69 | }; 70 | }; 71 | 72 | 1; 73 | -------------------------------------------------------------------------------- /t/integration/resource/Resource.pm: -------------------------------------------------------------------------------- 1 | package Resource; 2 | use strict; 3 | use warnings; 4 | 5 | use parent 'Test2::Harness::Runner::Resource'; 6 | 7 | my $limit = 2; 8 | 9 | my $no_slots_msg = 0; 10 | sub available { 11 | my $self = shift; 12 | my ($task) = @_; 13 | 14 | for my $slot (1 .. $limit) { 15 | return 1 unless defined $self->{$slot}; 16 | } 17 | 18 | $self->message("No Slots") unless $no_slots_msg++; 19 | return 0; 20 | } 21 | 22 | sub assign { 23 | my $self = shift; 24 | my ($task, $state) = @_; 25 | 26 | for my $slot (1 .. $limit) { 27 | next if defined $self->{$slot}; 28 | 29 | $self->message("Assigned: $task->{job_id} - $slot"); 30 | $state->{record} = $slot; 31 | $state->{env_vars}->{RESOURCE_TEST} = $slot; 32 | push @{$state->{args}} => $slot; 33 | 34 | return; 35 | } 36 | 37 | die "Error, no slots to assign"; 38 | } 39 | 40 | sub record { 41 | my $self = shift; 42 | my ($job_id, $slot) = @_; 43 | 44 | $self->message("Record: $job_id - $slot"); 45 | $self->{$slot} = $job_id; 46 | $self->{$job_id} = $slot; 47 | } 48 | 49 | sub release { 50 | my $self = shift; 51 | my ($job_id) = @_; 52 | 53 | my $slot = delete $self->{$job_id}; 54 | delete $self->{$slot}; 55 | $self->message("Release: $job_id - $slot"); 56 | } 57 | 58 | sub cleanup { 59 | my $self = shift; 60 | 61 | $self->message("RESOURCE CLEANUP"); 62 | } 63 | 64 | my $pid; 65 | sub message { 66 | my $self = shift; 67 | my ($msg) = @_; 68 | 69 | if (!$pid || $$ != $pid) { 70 | $pid = $$; 71 | 72 | print "$$ - $0\n"; 73 | } 74 | 75 | print "$$ - $msg\n"; 76 | } 77 | 78 | 1; 79 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/abort.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::abort; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Time::HiRes qw/sleep/; 8 | use Term::Table; 9 | 10 | use File::Spec(); 11 | 12 | use App::Yath::Util qw/find_pfile/; 13 | 14 | use Test2::Harness::Runner::State; 15 | use Test2::Harness::Util::File::JSON(); 16 | use Test2::Harness::Util::Queue(); 17 | 18 | use Test2::Harness::Util qw/open_file/; 19 | 20 | use parent 'App::Yath::Command::status'; 21 | use Test2::Harness::Util::HashBase; 22 | 23 | sub group { 'persist' } 24 | 25 | sub summary { "Abort all currently running or queued tests without killing the runner" } 26 | sub cli_args { "" } 27 | 28 | sub description { 29 | return <<" EOT"; 30 | This command will kill all running tests and clear the queue, but will not close the runner. 31 | EOT 32 | } 33 | 34 | sub pfile_params { (no_fatal => 1) } 35 | 36 | sub run { 37 | my $self = shift; 38 | 39 | # Get the output from finding the pfile 40 | $self->pfile_data(); 41 | 42 | my $state = Test2::Harness::Runner::State->new( 43 | workdir => $self->workdir, 44 | observe => 1, 45 | ); 46 | 47 | $state->poll; 48 | print "\nTruncating Queue...\n\n"; 49 | $state->truncate; 50 | $state->poll; 51 | 52 | my $running = $state->running_tasks; 53 | for my $task (values %$running) { 54 | my $pid = $self->get_job_pid($task->{run_id}, $task->{job_id}) // next;; 55 | my $file = $task->{rel_file}; 56 | print "Killing test $pid - $file...\n"; 57 | kill('INT', $pid); 58 | } 59 | 60 | print "\n"; 61 | return 0; 62 | } 63 | 64 | 1; 65 | 66 | __END__ 67 | 68 | =head1 POD IS AUTO-GENERATED 69 | 70 | -------------------------------------------------------------------------------- /t/integration/replay.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | use Test2::Harness::Util::File::JSONL; 8 | 9 | use Test2::Harness::Util::JSON qw/decode_json/; 10 | 11 | my $dir = __FILE__; 12 | $dir =~ s{\.t$}{}g; 13 | $dir =~ s{^\./}{}; 14 | 15 | sub clean_output { 16 | my $out = shift; 17 | 18 | $out->{output} =~ s/^.*duration.*$//m; 19 | $out->{output} =~ s/^.*Wrote log file:.*$//m; 20 | $out->{output} =~ s/^.*Symlinked to:.*$//m; 21 | $out->{output} =~ s/^\s*Wall Time:.*seconds//m; 22 | $out->{output} =~ s/^\s*CPU Time:.*s\)//m; 23 | $out->{output} =~ s/^\s*CPU Usage:.*%//m; 24 | $out->{output} =~ s/^\s*-+$//m; 25 | $out->{output} =~ s/^\s+$//m; 26 | $out->{output} =~ s/\n+/\n/g; 27 | $out->{output} =~ s/^\s+//mg; 28 | } 29 | 30 | my $out1 = yath( 31 | command => 'test', 32 | args => [$dir, '--ext=tx'], 33 | log => 1, 34 | exit => T(), 35 | test => sub { 36 | my $out = shift; 37 | clean_output($out); 38 | 39 | like($out->{output}, qr{FAILED.*fail\.tx}, "'fail.tx' was seen as a failure when reading the log"); 40 | like($out->{output}, qr{PASSED.*pass\.tx}, "'pass.tx' was not seen as a failure when reading the log"); 41 | 42 | }, 43 | ); 44 | 45 | my $logfile = $out1->{log}->name; 46 | 47 | yath( 48 | command => 'replay', 49 | args => [$logfile], 50 | exit => $out1->{exit}, 51 | test => sub { 52 | my $out2 = shift; 53 | clean_output($out2); 54 | clean_output($out1); 55 | 56 | is($out2->{output}, $out1->{output}, "Replay has identical output to original"); 57 | }, 58 | ); 59 | 60 | done_testing; 61 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/init.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::init; 2 | use strict; 3 | use warnings; 4 | 5 | use parent 'App::Yath::Command'; 6 | 7 | our $VERSION = '1.000162'; 8 | 9 | use Test2::Harness::Util qw/open_file/; 10 | use App::Yath::Util qw/is_generated_test_pl/; 11 | 12 | sub group { 'zinit' } 13 | 14 | sub summary { "Create/update test.pl to run tests via Test2::Harness" } 15 | 16 | sub description { 17 | return <<" EOT"; 18 | This command will create or update the 'test.pl' file in the current directory. 19 | This 'test.pl' file this creates will run all your tests via yath. 20 | 21 | This command will fail if there is already a test.pl file that does not look 22 | like it was generated by this command. 23 | EOT 24 | } 25 | 26 | sub run { 27 | die "'test.pl' already exists, and does not appear to be a yath runner.\n" 28 | if -f 'test.pl' && !is_generated_test_pl('test.pl'); 29 | 30 | print "\nWriting test.pl...\n\n"; 31 | 32 | my $fh = open_file('test.pl', '>'); 33 | 34 | print $fh <<' EOT'; 35 | #!/usr/bin/env perl 36 | # HARNESS-NO-PRELOAD 37 | # HARNESS-CAT-LONG 38 | # THIS IS A GENERATED YATH RUNNER TEST 39 | use strict; 40 | use warnings; 41 | 42 | use lib 'lib'; 43 | use App::Yath::Util qw/find_yath/; 44 | 45 | system($^X, find_yath(), '-D', 'test', '--default-search' => './t', '--default-search' => './t2', @ARGV); 46 | my $exit = $?; 47 | 48 | # This makes sure it works with prove. 49 | print "1..1\n"; 50 | print "not " if $exit; 51 | print "ok 1 - Passed tests when run by yath\n"; 52 | print STDERR "yath exited with $exit" if $exit; 53 | 54 | exit($exit ? 255 : 0); 55 | EOT 56 | 57 | return 0; 58 | } 59 | 60 | 1; 61 | 62 | 63 | __END__ 64 | 65 | =head1 POD IS AUTO-GENERATED 66 | 67 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Runner/Spawn.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Runner::Spawn; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use parent 'Test2::Harness::Runner::Job'; 8 | use Test2::Harness::Util::HashBase; 9 | 10 | sub init { 11 | my $self = shift; 12 | 13 | $self->{+RUN} //= Test2::Harness::Runner::Spawn::Run->new(); 14 | } 15 | 16 | sub out_file { sprintf('/proc/%i/fd/1', $_[0]->{+TASK}->{owner}) } 17 | sub err_file { sprintf('/proc/%i/fd/2', $_[0]->{+TASK}->{owner}) } 18 | sub in_file { undef } 19 | 20 | sub args { @{$_[0]->{+TASK}->{args} //= []} } 21 | 22 | sub job_dir { "" } 23 | sub run_dir { "" } 24 | 25 | sub use_stream { 0 } 26 | sub event_uuids { 0 } 27 | sub mem_usage { 0 } 28 | sub io_events { 0 } 29 | 30 | # These return lists 31 | sub load_import { } 32 | sub load { } 33 | 34 | package Test2::Harness::Runner::Spawn::Run; 35 | 36 | sub new { bless {}, shift }; 37 | 38 | sub env_vars { {} } 39 | 40 | sub AUTOLOAD { } 41 | 42 | 1; 43 | 44 | __END__ 45 | 46 | 47 | =pod 48 | 49 | =encoding UTF-8 50 | 51 | =head1 NAME 52 | 53 | Test2::Harness::Runner::Spawn - Minimal job class used for spawning processes 54 | 55 | =head1 DESCRIPTION 56 | 57 | Do not use this directly... 58 | 59 | =head1 SOURCE 60 | 61 | The source code repository for Test2-Harness can be found at 62 | F. 63 | 64 | =head1 MAINTAINERS 65 | 66 | =over 4 67 | 68 | =item Chad Granum Eexodist@cpan.orgE 69 | 70 | =back 71 | 72 | =head1 AUTHORS 73 | 74 | =over 4 75 | 76 | =item Chad Granum Eexodist@cpan.orgE 77 | 78 | =back 79 | 80 | =head1 COPYRIGHT 81 | 82 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 83 | 84 | This program is free software; you can redistribute it and/or 85 | modify it under the same terms as Perl itself. 86 | 87 | See F 88 | 89 | =cut 90 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/collector.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::collector; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use File::Spec; 8 | 9 | use App::Yath::Util qw/isolate_stdout/; 10 | 11 | use Test2::Harness::Util::JSON qw/decode_json/; 12 | use Test2::Harness::Util qw/mod2file/; 13 | 14 | use Test2::Harness::Run; 15 | 16 | use parent 'App::Yath::Command'; 17 | use Test2::Harness::Util::HashBase; 18 | 19 | sub internal_only { 1 } 20 | sub summary { "For internal use only" } 21 | sub name { 'collector' } 22 | 23 | sub run { 24 | my $self = shift; 25 | my ($collector_class, $dir, $run_id, $runner_pid, %args) = @{$self->{+ARGS}}; 26 | 27 | my $name = 'yath-collector'; 28 | $name = "$args{procname_prefix}-${name}" if $args{procname_prefix}; 29 | $0 = $name; 30 | 31 | my $fh = isolate_stdout(); 32 | 33 | my $settings = Test2::Harness::Settings->new(File::Spec->catfile($dir, 'settings.json')); 34 | 35 | require(mod2file($collector_class)); 36 | 37 | my $run = Test2::Harness::Run->new(%{decode_json()}); 38 | 39 | my $collector = $collector_class->new( 40 | %args, 41 | settings => $settings, 42 | workdir => $dir, 43 | run_id => $run_id, 44 | runner_pid => $runner_pid, 45 | run => $run, 46 | # as_json may already have the json form of the event cached, if so 47 | # we can avoid doing an extra call to encode_json 48 | action => sub { print $fh defined($_[0]) ? $_[0]->as_json . "\n" : "null\n"; }, 49 | ); 50 | 51 | local $SIG{PIPE} = 'IGNORE'; 52 | my $ok = eval { $collector->process(); 1 }; 53 | my $err = $@; 54 | 55 | eval { print $fh "null\n"; 1 } or warn $@; 56 | 57 | die $err unless $ok; 58 | 59 | return 0; 60 | } 61 | 62 | 1; 63 | 64 | __END__ 65 | 66 | =head1 POD IS AUTO-GENERATED 67 | 68 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/ps.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::ps; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Term::Table(); 8 | use File::Spec(); 9 | 10 | use App::Yath::Util qw/find_pfile/; 11 | 12 | use Test2::Harness::Runner::State; 13 | use Test2::Harness::Util::File::JSON(); 14 | use Test2::Harness::Util::Queue(); 15 | 16 | use parent 'App::Yath::Command::status'; 17 | use Test2::Harness::Util::HashBase qw/queue/; 18 | 19 | sub group { 'persist' } 20 | 21 | sub summary { "Process list for the runner" } 22 | sub cli_args { "" } 23 | 24 | sub description { 25 | return <<" EOT"; 26 | List all running processes and runner stages. 27 | EOT 28 | } 29 | 30 | sub pfile_params { (no_fatal => 1) } 31 | 32 | sub run { 33 | my $self = shift; 34 | 35 | my $data = $self->pfile_data(); 36 | 37 | my $state = Test2::Harness::Runner::State->new( 38 | workdir => $self->workdir, 39 | observe => 1, 40 | ); 41 | 42 | $state->poll; 43 | 44 | my @jobs; 45 | 46 | my $stage_status = $state->stage_readiness // {}; 47 | for my $stage (keys %$stage_status) { 48 | my $pid = $stage_status->{$stage} // next; 49 | $pid = 'N/A' if $pid == 1; 50 | push @jobs => [$pid, "Runner Stage", $stage]; 51 | } 52 | 53 | my $running = $state->running_tasks; 54 | for my $task (values %$running) { 55 | my $pid = $self->get_job_pid($task->{run_id}, $task->{job_id}) // 'N/A'; 56 | my $file = $task->{rel_file}; 57 | push @jobs => [$pid, "Running Test", $file]; 58 | } 59 | 60 | my $process_table = Term::Table->new( 61 | collapse => 1, 62 | header => [qw/pid type name/], 63 | rows => [sort { $a->[0] <=> $b->[0] } @jobs], 64 | ); 65 | 66 | print "\n**** Running Processes ****\n"; 67 | print "$_\n" for $process_table->render; 68 | 69 | return 0; 70 | } 71 | 72 | 1; 73 | 74 | __END__ 75 | 76 | =head1 POD IS AUTO-GENERATED 77 | 78 | -------------------------------------------------------------------------------- /t/integration/encoding.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use App::Yath::Tester qw/yath/; 4 | use File::Temp qw/tempdir/; 5 | use Test2::Harness::Util::File::JSONL; 6 | 7 | use Test2::Harness::Util::JSON qw/decode_json/; 8 | 9 | my $dir = __FILE__; 10 | $dir =~ s{\.t$}{}g; 11 | $dir =~ s{^\./}{}; 12 | 13 | my $want = <<"EOT"; 14 | ( NOTE ) job 1 valid note [\x{201c}\x{201d}\x{ff}\x{ff}] 15 | ( NOTE ) job 1 valid note [\x{201c}\x{201d}] 16 | ( DIAG ) job 1 valid diag [\x{201c}\x{201d}\x{ff}\x{ff}] 17 | ( DIAG ) job 1 valid diag [\x{201c}\x{201d}] 18 | ( STDOUT ) job 1 valid stdout [\x{201c}\x{201d}\x{ff}\x{ff}] 19 | ( STDOUT ) job 1 valid stdout [\x{201c}\x{201d}] 20 | ( STDERR ) job 1 valid stderr [\x{201c}\x{201d}\x{ff}\x{ff}] 21 | ( STDERR ) job 1 valid stderr [\x{201c}\x{201d}] 22 | [ PASS ] job 1 + valid ok [\x{201c}\x{201d}\x{ff}\x{ff}] 23 | [ PASS ] job 1 + valid ok [\x{201c}\x{201d}] 24 | ( STDOUT ) job 1 STDOUT: M\x{101}kaha 25 | ( STDERR ) job 1 STDERR: M\x{101}kaha 26 | ( DIAG ) job 1 DIAG: M\x{101}kaha 27 | ( NOTE ) job 1 NOTE: M\x{101}kaha 28 | [ PASS ] job 1 + ASSERT: M\x{101}kaha 29 | [ PASS ] job 1 + \x{406} \x{449}\x{435} \x{442}\x{440}\x{43e}\x{445}\x{438} 30 | EOT 31 | 32 | yath( 33 | command => 'test', 34 | args => ['-v', "$dir/plugin.tx"], 35 | exit => 0, 36 | encoding => 'utf8', 37 | test => sub { 38 | my $out = shift; 39 | like($out->{output}, qr/\Q$want\E/, "Got proper codepoints"); 40 | }, 41 | ); 42 | 43 | yath( 44 | command => 'test', 45 | args => ['-v', "$dir/no-plugin.tx"], 46 | exit => 0, 47 | test => sub { 48 | my $out = shift; 49 | 50 | utf8::encode( my $raw_want = $want ); 51 | utf8::encode( my $u00ff = "\x{ff}" ); 52 | $raw_want =~ s<\Q$u00ff\E><\xff>g; 53 | 54 | like($out->{output}, qr/\Q$raw_want\E/, "Got proper codepoints"); 55 | }, 56 | ); 57 | 58 | done_testing; 59 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Util/File/JSONL.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Util::File::JSONL; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Carp qw/croak/; 8 | use Test2::Harness::Util::JSON qw/encode_json decode_json/; 9 | 10 | use parent 'Test2::Harness::Util::File::Stream'; 11 | use Test2::Harness::Util::HashBase; 12 | 13 | sub decode { shift; decode_json($_[0]) } 14 | sub encode { shift; encode_json(@_) . "\n" } 15 | 16 | 1; 17 | 18 | __END__ 19 | 20 | =pod 21 | 22 | =encoding UTF-8 23 | 24 | =head1 NAME 25 | 26 | Test2::Harness::Util::File::JSONL - Utility class for a JSONL file (stream) 27 | 28 | =head1 DESCRIPTION 29 | 30 | Subclass of L and 31 | L which automatically handles 32 | encoding/decoding JSONL data. 33 | 34 | =head1 SYNOPSIS 35 | 36 | use Test2::Harness::Util::File::JSONL; 37 | 38 | my $jsonl = Test2::Harness::Util::File::JSONL->new(name => '/path/to/file.jsonl'); 39 | 40 | while (1) { 41 | my @items = $jsonl->poll(max => 1000) or last; 42 | for my $item (@items) { 43 | ... handle $item ... 44 | } 45 | } 46 | 47 | or 48 | 49 | use Test2::Harness::Util::File::JSONL; 50 | 51 | my $jsonl = Test2::Harness::Util::File::JSONL->new(name => '/path/to/file.jsonl'); 52 | 53 | $jsonl->write({my => 'item', ... }); 54 | ... 55 | 56 | =head1 SEE ALSO 57 | 58 | See the base classes L and 59 | L for methods. 60 | 61 | =head1 SOURCE 62 | 63 | The source code repository for Test2-Harness can be found at 64 | F. 65 | 66 | =head1 MAINTAINERS 67 | 68 | =over 4 69 | 70 | =item Chad Granum Eexodist@cpan.orgE 71 | 72 | =back 73 | 74 | =head1 AUTHORS 75 | 76 | =over 4 77 | 78 | =item Chad Granum Eexodist@cpan.orgE 79 | 80 | =back 81 | 82 | =head1 COPYRIGHT 83 | 84 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 85 | 86 | This program is free software; you can redistribute it and/or 87 | modify it under the same terms as Perl itself. 88 | 89 | See F 90 | 91 | =cut 92 | -------------------------------------------------------------------------------- /release-scripts/generate_command_pod.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | die "No directory specified" unless @ARGV; 4 | chdir($ARGV[0]) or die "Could not chdir to $ARGV[0]"; 5 | 6 | unshift @INC => './lib'; 7 | 8 | my $base = './lib/App/Yath/Command'; 9 | 10 | opendir(my $dh, $base) or die "Could not open command dir!"; 11 | 12 | for my $file (readdir($dh)) { 13 | next unless $file =~ m/\.pm$/; 14 | my $fq = "$base/$file"; 15 | 16 | my $rel = $fq; 17 | $rel =~ s{^\./lib/}{}g; 18 | 19 | my $pkg = $rel; 20 | $pkg =~ s{/}{::}g; 21 | $pkg =~ s{\.pm$}{}g; 22 | 23 | require $rel; 24 | 25 | my $pod = $pkg->generate_pod or die "Could not get usage POD!"; 26 | 27 | $pod = join "\n\n" => start(), $pod, ending(); 28 | 29 | my $found; 30 | my @lines; 31 | open(my $fh, '<', $fq) or die "Could not open file '$fq' for reading: $!"; 32 | while(my $line = <$fh>) { 33 | if ($line eq "=head1 POD IS AUTO-GENERATED\n") { 34 | $found++; 35 | push @lines => $pod; 36 | next; 37 | } 38 | 39 | push @lines => $line; 40 | } 41 | close($fh); 42 | 43 | die "Could not find line to replace in $fq" unless $found; 44 | 45 | open($fh, '>', $fq) or die "Could not open file '$fq' for writing: $!"; 46 | print $fh @lines; 47 | close($fh); 48 | } 49 | 50 | sub start { 51 | return ("=pod", "=encoding UTF-8"); 52 | } 53 | 54 | sub ending { 55 | my ($sec,$min,$hour,$mday,$mon,$year) = localtime(); 56 | $year = $year + 1900; 57 | 58 | return <<" EOT" 59 | =head1 SOURCE 60 | 61 | The source code repository for Test2-Harness can be found at 62 | F. 63 | 64 | =head1 MAINTAINERS 65 | 66 | =over 4 67 | 68 | =item Chad Granum Eexodist\@cpan.orgE 69 | 70 | =back 71 | 72 | =head1 AUTHORS 73 | 74 | =over 4 75 | 76 | =item Chad Granum Eexodist\@cpan.orgE 77 | 78 | =back 79 | 80 | =head1 COPYRIGHT 81 | 82 | Copyright $year Chad Granum Eexodist7\@gmail.comE. 83 | 84 | This program is free software; you can redistribute it and/or 85 | modify it under the same terms as Perl itself. 86 | 87 | See F 88 | 89 | =cut 90 | EOT 91 | } 92 | -------------------------------------------------------------------------------- /release-scripts/generate_options_pod.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | 6 | die "No directory specified" unless @ARGV; 7 | chdir($ARGV[0]) or die "Could not chdir to $ARGV[0]"; 8 | 9 | unshift @INC => './lib'; 10 | 11 | require App::Yath::Command; 12 | 13 | for my $base ('./lib/App/Yath/Options', './lib/App/Yath/Plugin') { 14 | opendir(my $dh, $base) or die "Could not open dir '$base': $!"; 15 | 16 | for my $file (readdir($dh)) { 17 | next unless $file =~ m/\.pm$/; 18 | my $fq = "$base/$file"; 19 | 20 | my $rel = $fq; 21 | $rel =~ s{^\./lib/}{}g; 22 | 23 | my $pkg = $rel; 24 | $pkg =~ s{/}{::}g; 25 | $pkg =~ s{\.pm$}{}g; 26 | 27 | require $rel; 28 | 29 | next unless $pkg->can('options'); 30 | my $options = $pkg->options or next; 31 | delete $_->{applicable} for @{$options->all}; 32 | $options->set_command_class('App::Yath::Command'); 33 | my $pre_opts = $options->pre_docs('pod', 3); 34 | my $cmd_opts = $options->cmd_docs('pod', 3); 35 | die "No option docs for $file?" unless $pre_opts || $cmd_opts; 36 | 37 | my $pod = "=head1 PROVIDED OPTIONS\n\n"; 38 | 39 | if ($pre_opts) { 40 | $pod .= "=head2 YATH OPTIONS (PRE-COMMAND)\n\n"; 41 | $pod .= $pre_opts; 42 | } 43 | 44 | $pod .= "\n\n" if $pre_opts && $cmd_opts; 45 | 46 | if ($cmd_opts) { 47 | $pod .= "=head2 COMMAND OPTIONS\n\n"; 48 | $pod .= $cmd_opts; 49 | } 50 | 51 | $pod .= "\n"; 52 | 53 | my $found; 54 | my @lines; 55 | open(my $fh, '<', $fq) or die "Could not open file '$fq' for reading: $!"; 56 | while (my $line = <$fh>) { 57 | if ($line eq "=head1 PROVIDED OPTIONS POD IS AUTO-GENERATED\n") { 58 | $found++; 59 | push @lines => $pod; 60 | next; 61 | } 62 | 63 | push @lines => $line; 64 | } 65 | close($fh); 66 | 67 | next unless $found; 68 | 69 | open($fh, '>', $fq) or die "Could not open file '$fq' for writing: $!"; 70 | print $fh @lines; 71 | close($fh); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Util/Term.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Util::Term; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Test2::Util qw/IS_WIN32/; 8 | 9 | use Importer Importer => 'import'; 10 | our @EXPORT_OK = qw/USE_ANSI_COLOR/; 11 | 12 | { 13 | my $use = 0; 14 | local ($@, $!); 15 | 16 | if (eval { require Term::ANSIColor; Term::ANSIColor->VERSION('4.03') }) { 17 | if (IS_WIN32) { 18 | if (eval { require Win32::Console::ANSI }) { 19 | Win32::Console::ANSI->import(); 20 | $use = 1; 21 | } 22 | } 23 | else { 24 | $use = 1; 25 | } 26 | } 27 | 28 | if ($use) { 29 | *USE_ANSI_COLOR = sub() { 1 }; 30 | } 31 | else { 32 | *USE_ANSI_COLOR = sub() { 0 }; 33 | } 34 | } 35 | 36 | 1; 37 | 38 | __END__ 39 | 40 | =pod 41 | 42 | =encoding UTF-8 43 | 44 | =head1 NAME 45 | 46 | Test2::Harness::Util::Term - Terminal utilities for Test2::Harness 47 | 48 | =head1 DESCRIPTION 49 | 50 | This module provides information about the terminal in which the harness is 51 | running. 52 | 53 | =head1 SYNOPSIS 54 | 55 | use Test2::Harness::Util::Term qw/USE_ANSI_COLOR/; 56 | 57 | if (USE_ANSI_COLOR) { 58 | ... 59 | } 60 | else { 61 | ... 62 | } 63 | 64 | =head1 EXPORTS 65 | 66 | =over 4 67 | 68 | =item $bool = USE_ANSI_COLOR() 69 | 70 | True if L is available and usable. 71 | 72 | =back 73 | 74 | =head1 SOURCE 75 | 76 | The source code repository for Test2-Harness can be found at 77 | F. 78 | 79 | =head1 MAINTAINERS 80 | 81 | =over 4 82 | 83 | =item Chad Granum Eexodist@cpan.orgE 84 | 85 | =back 86 | 87 | =head1 AUTHORS 88 | 89 | =over 4 90 | 91 | =item Chad Granum Eexodist@cpan.orgE 92 | 93 | =back 94 | 95 | =head1 COPYRIGHT 96 | 97 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 98 | 99 | This program is free software; you can redistribute it and/or 100 | modify it under the same terms as Perl itself. 101 | 102 | See F 103 | 104 | =cut 105 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Settings.t: -------------------------------------------------------------------------------- 1 | use Test2::V0 -target => 'Test2::Harness::Settings'; 2 | use File::Temp qw/tempfile/; 3 | use Test2::Harness::Util::JSON qw/encode_json/; 4 | 5 | my $one = $CLASS->new(); 6 | isa_ok($one, [$CLASS], "Created an instance"); 7 | 8 | ok(!$one->check_prefix('foo'), "foo is not defined"); 9 | like(dies { $one->foo }, qr/The 'foo' prefix is not defined/, "Cannot call foo if it is not defined"); 10 | like(dies { $one->prefix('foo') }, qr/The 'foo' prefix is not defined/, "Cannot call prefix(foo) if it is not defined"); 11 | 12 | $one->define_prefix('foo'); 13 | isa_ok($one->foo, ['Test2::Harness::Settings::Prefix'], "Defined the prefix"); 14 | ok($one->check_prefix('foo'), "foo is now defined"); 15 | ok($one->foo, "Can call foo if it is defined"); 16 | ok($one->prefix('foo'), "Can call prefix(foo) if it is defined"); 17 | 18 | is($one->TO_JSON, {foo => exact_ref($one->foo)}, "TO_JSON"); 19 | 20 | like(dies { $CLASS->foo }, qr/Method foo\(\) must be called on a blessed instance/, "Need a blessed instance"); 21 | like(dies { $one->foo(1) }, qr/Too many arguments for foo\(\)/, "No args"); 22 | 23 | { 24 | $INC{'XXX.pm'} = __FILE__; 25 | package XXX; 26 | sub new { shift; bless {@_}, 'XXX' }; 27 | } 28 | 29 | $one->foo->vivify_field('xxx'); 30 | $one->foo->field(xxx => 'yyy'); 31 | 32 | my $thing = $one->build('foo', 'XXX', a => 'b'); 33 | isa_ok($thing, ['XXX'], "Got a blessed instance of XXX"); 34 | is( 35 | $thing, 36 | { 37 | a => 'b', 38 | xxx => 'yyy', 39 | }, 40 | "Instance is composed as expected" 41 | ); 42 | 43 | my ($fh, $name) = tempfile(UNLINK => 1); 44 | print $fh encode_json($one); 45 | close($fh); 46 | 47 | my $two = $CLASS->new($name); 48 | isa_ok($two, [$CLASS], "Correct class"); 49 | is($two, $one, "Serialized and deserialized round trip"); 50 | ref_is_not($two, $one, "2 different refs"); 51 | 52 | like( 53 | dies { $CLASS->new(foo => []) }, 54 | qr/All prefixes must be defined as hashes/, 55 | "Prefixes must be hashes" 56 | ); 57 | 58 | like( 59 | dies { $CLASS->new(foo => bless({}, 'XXX')) }, 60 | qr/All prefixes must contain instances of Test2::Harness::Settings::Prefix/, 61 | "Blessed Prefixes must be prefixes" 62 | ); 63 | 64 | done_testing; 65 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Util/File/JSON.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Util::File::JSON; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Carp qw/croak confess/; 8 | use Test2::Harness::Util::JSON qw/encode_json decode_json encode_pretty_json/; 9 | 10 | use parent 'Test2::Harness::Util::File'; 11 | use Test2::Harness::Util::HashBase qw/pretty/; 12 | 13 | sub decode { shift; decode_json(@_) } 14 | sub encode { shift->pretty ? encode_pretty_json(@_) : encode_json(@_) } 15 | 16 | sub reset { croak "line reading is disabled for json files" } 17 | sub read_line { croak "line reading is disabled for json files" } 18 | 19 | sub maybe_read { 20 | my $self = shift; 21 | 22 | return undef unless -e $self->{+NAME}; 23 | my $out = Test2::Harness::Util::read_file($self->{+NAME}); 24 | 25 | return undef unless defined($out) && length($out); 26 | 27 | eval { $out = $self->decode($out); 1 } or confess "$self->{+NAME}: $@"; 28 | return $out; 29 | } 30 | 31 | 1; 32 | 33 | __END__ 34 | 35 | =pod 36 | 37 | =encoding UTF-8 38 | 39 | =head1 NAME 40 | 41 | Test2::Harness::Util::File::JSON - Utility class for a JSON file. 42 | 43 | =head1 DESCRIPTION 44 | 45 | Subclass of L which automatically handles 46 | encoding/decoding JSON data. 47 | 48 | =head1 SYNOPSIS 49 | 50 | require Test2::Harness::Util::File::JSON; 51 | my $file = Test2::Harness::Util::File::JSON->new(name => '/path/to/file.json'); 52 | 53 | $hash = $file->read; 54 | # or 55 | $$file->write({...}); 56 | 57 | =head1 SEE ALSO 58 | 59 | See the base class L for methods. 60 | 61 | =head1 SOURCE 62 | 63 | The source code repository for Test2-Harness can be found at 64 | F. 65 | 66 | =head1 MAINTAINERS 67 | 68 | =over 4 69 | 70 | =item Chad Granum Eexodist@cpan.orgE 71 | 72 | =back 73 | 74 | =head1 AUTHORS 75 | 76 | =over 4 77 | 78 | =item Chad Granum Eexodist@cpan.orgE 79 | 80 | =back 81 | 82 | =head1 COPYRIGHT 83 | 84 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 85 | 86 | This program is free software; you can redistribute it and/or 87 | modify it under the same terms as Perl itself. 88 | 89 | See F 90 | 91 | =cut 92 | -------------------------------------------------------------------------------- /lib/App/Yath/Options/Collector.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Options::Collector; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use App::Yath::Options; 8 | 9 | option_group {prefix => 'collector', category => "Collector Options"} => sub { 10 | option max_open_jobs => ( 11 | type => 's', 12 | description => 'Maximum number of jobs a collector can process at a time, if more jobs are pending their output will be delayed until the earlier jobs have been processed. (Default: double the -j value)', 13 | long_examples => [' 18'], 14 | short_examples => [' 18'], 15 | ); 16 | 17 | option max_poll_events => ( 18 | type => 's', 19 | description => 'Maximum number of events to poll from a job before jumping to the next job. (Default: 1000)', 20 | default => 1000, 21 | long_examples => [' 1000'], 22 | short_examples => [' 1000'], 23 | ); 24 | 25 | post \&collector_post; 26 | }; 27 | 28 | sub collector_post { 29 | my %params = @_; 30 | my $settings = $params{settings}; 31 | 32 | unless ($settings->collector->max_open_jobs) { 33 | my $j = $settings->runner->job_count // 1; 34 | my $max_open = 2 * $j; 35 | $settings->collector->field(max_open_jobs => $max_open); 36 | } 37 | } 38 | 39 | 40 | 1; 41 | 42 | __END__ 43 | 44 | 45 | =pod 46 | 47 | =encoding UTF-8 48 | 49 | =head1 NAME 50 | 51 | App::Yath::Options::Collector - collector options for Yath. 52 | 53 | =head1 DESCRIPTION 54 | 55 | This is where the command line options for the collector are defined. 56 | 57 | =head1 PROVIDED OPTIONS POD IS AUTO-GENERATED 58 | 59 | =head1 SOURCE 60 | 61 | The source code repository for Test2-Harness can be found at 62 | F. 63 | 64 | =head1 MAINTAINERS 65 | 66 | =over 4 67 | 68 | =item Chad Granum Eexodist@cpan.orgE 69 | 70 | =back 71 | 72 | =head1 AUTHORS 73 | 74 | =over 4 75 | 76 | =item Chad Granum Eexodist@cpan.orgE 77 | 78 | =back 79 | 80 | =head1 COPYRIGHT 81 | 82 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 83 | 84 | This program is free software; you can redistribute it and/or 85 | modify it under the same terms as Perl itself. 86 | 87 | See F 88 | 89 | =cut 90 | -------------------------------------------------------------------------------- /t/integration/reload_syntax_error.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use Test2::Require::AuthorTesting; 3 | 4 | use File::Temp qw/tempdir/; 5 | use File::Spec; 6 | 7 | use App::Yath::Tester qw/yath/; 8 | use Test2::Harness::Util::File::JSONL; 9 | use Test2::Harness::Util qw/clean_path/; 10 | 11 | use Test2::Harness::Util::JSON qw/decode_json/; 12 | 13 | use Test2::Util qw/CAN_REALLY_FORK/; 14 | skip_all "Cannot fork, skipping preload test" 15 | if $ENV{T2_NO_FORK} || !CAN_REALLY_FORK; 16 | 17 | my $tx = __FILE__ . 'x'; 18 | 19 | my $tmpdir = tempdir(CLEANUP => 1); 20 | mkdir("$tmpdir/Preload") or die "($tmpdir/Preload) $!"; 21 | 22 | { 23 | open(my $fh, '>', "$tmpdir/Preload.pm") or die "Could not create preload: $!"; 24 | print $fh <<' EOT'; 25 | package Preload; 26 | use strict; 27 | use warnings; 28 | 29 | use Test2::Harness::Runner::Preload; 30 | 31 | stage A => sub { 32 | default(); 33 | 34 | # Do like this to avoid blacklisting 35 | preload sub { require Preload::Flux }; 36 | }; 37 | 38 | 1; 39 | EOT 40 | } 41 | 42 | sub touch { 43 | my ($inject) = @_; 44 | my $path = "$tmpdir/Preload/Flux.pm"; 45 | note "Touching $path..."; 46 | sleep 1; 47 | 48 | open(my $fh, '>', $path) or die $!; 49 | 50 | print $fh <<" EOT"; 51 | package Preload::Flux; 52 | use strict; 53 | use warnings; 54 | 55 | sub foo { 'foo' } 56 | 57 | $inject 58 | 59 | 1; 60 | EOT 61 | 62 | close($fh); 63 | 64 | sleep 2; 65 | } 66 | 67 | touch('$Preload::Flux::VAR = "initial";'); 68 | 69 | yath( 70 | command => 'start', 71 | pre => ["-D$tmpdir"], 72 | args => ["-I$tmpdir", '-PPreload'], 73 | debug => 2, 74 | exit => 0, 75 | ); 76 | 77 | yath( 78 | command => 'run', 79 | args => [$tx, '::', 'initial'], 80 | exit => 0, 81 | ); 82 | 83 | touch('$Preload::Flux::VAR = "Syntax Error $bob";'); 84 | 85 | yath( 86 | command => 'run', 87 | args => [$tx], # no arg, so undef 88 | exit => 0, 89 | ); 90 | 91 | touch('$Preload::Flux::VAR = "fixed";'); 92 | 93 | yath( 94 | command => 'run', 95 | args => [$tx, '::', 'fixed'], 96 | exit => 0, 97 | ); 98 | 99 | 100 | yath(command => 'stop', exit => 0); 101 | 102 | done_testing; 103 | -------------------------------------------------------------------------------- /t/integration/smoke.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | use Test2::Harness::Util::File::JSONL; 8 | 9 | use Test2::Harness::Util::JSON qw/decode_json/; 10 | 11 | my $dir = __FILE__; 12 | $dir =~ s{\.t$}{}g; 13 | $dir =~ s{^\./}{}; 14 | 15 | yath( 16 | command => 'test', 17 | pre => ['-p+SmokePlugin'], 18 | args => [$dir, '--ext=tx'], 19 | log => 1, 20 | exit => 0, 21 | test => \&the_test, 22 | ); 23 | 24 | yath( 25 | command => 'test', 26 | pre => ['-p+SmokePlugin'], 27 | args => [$dir, '-j3', '--ext=tx'], 28 | log => 1, 29 | exit => 0, 30 | test => \&the_test, 31 | ); 32 | 33 | sub the_test { 34 | my $out = shift; 35 | my $log = $out->{log}; 36 | 37 | my @order; 38 | my @events = $log->poll(); 39 | while (@events) { 40 | if (my $event = shift @events) { 41 | my $f = $event->{facet_data}; 42 | 43 | if (my $l = $f->{harness_job_start}) { 44 | push @order => $l; 45 | } 46 | } 47 | 48 | # Check for additional events, probably should not have any, but we may hit 49 | # a buffering limit in the log reader and need additional polls. 50 | push @events => $log->poll; 51 | } 52 | 53 | # We care about the order in which events happened based on time stamp, not the 54 | # order in which they were collected, which may be different. Here we will sort 55 | # based on stamp. 56 | @order = sort { $a->{stamp} <=> $b->{stamp} } @order; 57 | 58 | is( 59 | [map { $_->{rel_file} } @order[0 .. 3]], 60 | bag { 61 | item match qr/a\.tx$/; 62 | item match qr/c\.tx$/; 63 | item match qr/e\.tx$/; 64 | item match qr/g\.tx$/; 65 | end; 66 | }, 67 | "The 4 smoke tests ran first" 68 | ); 69 | 70 | is( 71 | [map { $_->{rel_file} } @order[4 .. 7]], 72 | bag { 73 | item match qr/b\.tx$/; 74 | item match qr/d\.tx$/; 75 | item match qr/f\.tx$/; 76 | item match qr/h\.tx$/; 77 | end; 78 | }, 79 | "The 4 non-smoke tests ran later" 80 | ); 81 | } 82 | 83 | done_testing; 84 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Util/File/Value.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Util::File::Value; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use parent 'Test2::Harness::Util::File'; 8 | use Test2::Harness::Util::HashBase; 9 | 10 | sub init { 11 | my $self = shift; 12 | $self->{+DONE} = 1; 13 | } 14 | 15 | sub read { 16 | my $self = shift; 17 | my $out = $self->SUPER::read(@_); 18 | chomp($out) if defined $out; 19 | return $out; 20 | } 21 | 22 | sub read_line { 23 | my $self = shift; 24 | my $out = $self->SUPER::read_line(@_); 25 | chomp($out) if defined $out; 26 | return $out; 27 | } 28 | 29 | 1; 30 | 31 | __END__ 32 | 33 | =pod 34 | 35 | =encoding UTF-8 36 | 37 | =head1 NAME 38 | 39 | Test2::Harness::Util::File::Value - Utility class for a file that contains 40 | exactly 1 value. 41 | 42 | =head1 DESCRIPTION 43 | 44 | This is a subclass of L for files expected to have 45 | exactly 1 value stored in them. 46 | 47 | =head1 SYNOPSIS 48 | 49 | use Test2::Harness::Util::File::Value; 50 | 51 | my $vf = Test2::Harness::Util::File::Value->new(name => 'path/to/file'); 52 | my $val = $vf->read; 53 | 54 | =head1 METHODS 55 | 56 | =over 4 57 | 58 | =item $val = $vf->read() 59 | 60 | Read all contents from the file, C it, and return it. 61 | 62 | =item $val = $vf->read_line() 63 | 64 | Read the first line from the file, C it, and return it. Note, this 65 | may not return anything if the value in the file does not terminate with a 66 | newline. 67 | 68 | =back 69 | 70 | =head1 SOURCE 71 | 72 | The source code repository for Test2-Harness can be found at 73 | F. 74 | 75 | =head1 MAINTAINERS 76 | 77 | =over 4 78 | 79 | =item Chad Granum Eexodist@cpan.orgE 80 | 81 | =back 82 | 83 | =head1 AUTHORS 84 | 85 | =over 4 86 | 87 | =item Chad Granum Eexodist@cpan.orgE 88 | 89 | =back 90 | 91 | =head1 COPYRIGHT 92 | 93 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 94 | 95 | This program is free software; you can redistribute it and/or 96 | modify it under the same terms as Perl itself. 97 | 98 | See F 99 | 100 | =cut 101 | -------------------------------------------------------------------------------- /lib/Test2/Harness/Runner/Run.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::Runner::Run; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Carp qw/croak/; 8 | use File::Spec(); 9 | 10 | use Test2::Harness::Util::File::JSONL; 11 | 12 | use parent 'Test2::Harness::Run'; 13 | use Test2::Harness::Util::HashBase qw{ 14 | SUPER::init(); 25 | 26 | croak "'workdir' is a required attribute" unless $self->{+WORKDIR}; 27 | } 28 | 29 | sub run_dir { $_[0]->{+RUN_DIR} //= $_[0]->SUPER::run_dir($_[0]->{+WORKDIR}) } 30 | sub jobs_file { $_[0]->{+JOBS_FILE} //= File::Spec->catfile($_[0]->run_dir, 'jobs.jsonl') } 31 | sub jobs { $_[0]->{+JOBS} //= Test2::Harness::Util::File::JSONL->new(name => $_[0]->jobs_file, use_write_lock => 1) } 32 | 33 | 1; 34 | 35 | __END__ 36 | 37 | 38 | =pod 39 | 40 | =encoding UTF-8 41 | 42 | =head1 NAME 43 | 44 | Test2::Harness::Runner::Run - Runner specific subclass of a test run. 45 | 46 | =head1 DESCRIPTION 47 | 48 | Runner subclass of L for use inside the runner. 49 | 50 | =head1 METHODS 51 | 52 | In addition to the methods provided by L, these are provided. 53 | 54 | =over 4 55 | 56 | =item $dir = $run->workdir 57 | 58 | Runner directory. 59 | 60 | =item $dir = $run->run_dir 61 | 62 | Directory specific to this run. 63 | 64 | =item $path = $run->jobs_file 65 | 66 | Path to the C file. 67 | 68 | =item $fh = $run->jobs 69 | 70 | Filehandle to C. 71 | 72 | =back 73 | 74 | =head1 SOURCE 75 | 76 | The source code repository for Test2-Harness can be found at 77 | F. 78 | 79 | =head1 MAINTAINERS 80 | 81 | =over 4 82 | 83 | =item Chad Granum Eexodist@cpan.orgE 84 | 85 | =back 86 | 87 | =head1 AUTHORS 88 | 89 | =over 4 90 | 91 | =item Chad Granum Eexodist@cpan.orgE 92 | 93 | =back 94 | 95 | =head1 COPYRIGHT 96 | 97 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 98 | 99 | This program is free software; you can redistribute it and/or 100 | modify it under the same terms as Perl itself. 101 | 102 | See F 103 | 104 | =cut 105 | -------------------------------------------------------------------------------- /.github/workflows/testsuite.yml: -------------------------------------------------------------------------------- 1 | name: testsuite 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | 13 | # 14 | # A basic and simple test run 15 | # 16 | 17 | ubuntu: 18 | name: "simple testrun" 19 | env: 20 | PERL_USE_UNSAFE_INC: 0 21 | AUTHOR_TESTING: 1 22 | AUTOMATED_TESTING: 1 23 | RELEASE_TESTING: 1 24 | PERL_CARTON_PATH: $GITHUB_WORKSPACE/local 25 | 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | - run: perl -V 31 | - name: install dependencies from cpanfile.ci 32 | uses: perl-actions/install-with-cpm@v1 33 | with: 34 | cpanfile: "cpanfile.ci" 35 | - name: install dependencies from cpanfile 36 | uses: perl-actions/install-with-cpm@v1 37 | with: 38 | cpanfile: "cpanfile" 39 | - run: perl Makefile.PL 40 | - run: make 41 | - run: make test 42 | 43 | # 44 | # Only trigger the matrix if the testsuite pass above 45 | # 46 | 47 | perl-versions: 48 | runs-on: ubuntu-latest 49 | name: List Perl versions 50 | outputs: 51 | perl-versions: ${{ steps.action.outputs.perl-versions }} 52 | steps: 53 | - id: action 54 | uses: perl-actions/perl-versions@v1 55 | with: 56 | since-perl: v5.10 57 | with-devel: true 58 | 59 | perl: 60 | name: "Perl v${{ matrix.perl-version }}" 61 | needs: [ubuntu,perl-versions] 62 | runs-on: ubuntu-latest 63 | 64 | env: 65 | PERL_USE_UNSAFE_INC: 0 66 | AUTHOR_TESTING: 1 67 | AUTOMATED_TESTING: 1 68 | RELEASE_TESTING: 1 69 | PERL_CARTON_PATH: $GITHUB_WORKSPACE/local 70 | T2_NO_FORK: 1 71 | 72 | strategy: 73 | fail-fast: false 74 | matrix: 75 | perl-version: ${{ fromJson (needs.perl-versions.outputs.perl-versions) }} 76 | 77 | container: perldocker/perl-tester:${{ matrix.perl-version }} 78 | 79 | steps: 80 | - uses: actions/checkout@v4 81 | - run: perl -V 82 | - name: Install Dependencies 83 | run: | 84 | cpm install -g --show-build-log-on-failure --cpanfile cpanfile.ci 85 | cpm install -g --show-build-log-on-failure --cpanfile cpanfile 86 | - run: perl Makefile.PL 87 | - run: make 88 | - run: make test 89 | -------------------------------------------------------------------------------- /xt/author/pod-spell.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test2::Require::AuthorTesting; 5 | use Test2::Require::Module 'Test::Spelling'; 6 | use Test::Spelling; 7 | 8 | my @stopwords; 9 | for () { 10 | chomp; 11 | push @stopwords, $_ 12 | unless /\A (?: \# | \s* \z)/msx; # skip comments, whitespace 13 | } 14 | 15 | print "### adding stopwords @stopwords\n"; 16 | 17 | add_stopwords(@stopwords); 18 | local $ENV{LC_ALL} = 'C'; 19 | set_spell_cmd('aspell list -l en'); 20 | all_pod_files_spelling_ok; 21 | 22 | __DATA__ 23 | ## personal names 24 | binkley 25 | Bowden 26 | Daly 27 | dfs 28 | Eryq 29 | EXODIST 30 | Fergal 31 | Glew 32 | Granum 33 | Oxley 34 | Pritikin 35 | Schwern 36 | Skoll 37 | Slaymaker 38 | ZeeGee 39 | 40 | ## proper names 41 | Fennec 42 | ICal 43 | xUnit 44 | 45 | ## test jargon 46 | Diag 47 | diag 48 | isnt 49 | subtest 50 | subtests 51 | testsuite 52 | testsuites 53 | TODO 54 | todo 55 | todos 56 | untestable 57 | EventFacet 58 | EventFacets 59 | renderers 60 | xt 61 | 62 | ## computerese 63 | blackbox 64 | BUF 65 | codeblock 66 | combinatorics 67 | dir 68 | getline 69 | getlines 70 | getpos 71 | Getter 72 | getters 73 | HashBase 74 | heisenbug 75 | IPC 76 | NBYTES 77 | param 78 | perlish 79 | perl-qa 80 | POS 81 | predeclaring 82 | rebless 83 | refactoring 84 | refcount 85 | Reinitializes 86 | SCALARREF 87 | setpos 88 | Setter 89 | SHM 90 | sref 91 | subevent 92 | subevents 93 | testability 94 | TIEHANDLE 95 | tie-ing 96 | unoverload 97 | VMS 98 | vmsperl 99 | YESNO 100 | ansi 101 | html 102 | HASHBASE 103 | renderer 104 | SHBANG 105 | JSONL 106 | YATH 107 | jsonl 108 | rc 109 | tmpdir 110 | utils 111 | workdir 112 | Postfix 113 | env 114 | bz 115 | bzip 116 | preloaded 117 | Yath 118 | vv 119 | PRELOAD 120 | yath 121 | preloads 122 | Preload 123 | pPlugin 124 | tlib 125 | preload 126 | loadim 127 | preloading 128 | shm 129 | qvf 130 | mem 131 | 132 | 133 | ## other jargon, slang 134 | 17th 135 | AHHHHHHH 136 | Dummy 137 | globalest 138 | Hmmm 139 | cid 140 | tid 141 | pid 142 | SIGINT 143 | SIGALRM 144 | SIGHUP 145 | SIGTERM 146 | SIGUSR1 147 | SIGUSR2 148 | webhook 149 | integrations 150 | IMMISCIBLE 151 | POSTEXIT 152 | lff 153 | backfill 154 | ESYNC 155 | muxed 156 | 157 | 158 | ## Spelled correctly according to google: 159 | recognise 160 | recognises 161 | judgement 162 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/help.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::help; 2 | use strict; 3 | use warnings; 4 | 5 | use Test2::Util qw/pkg_to_file/; 6 | 7 | our $VERSION = '1.000162'; 8 | 9 | use parent 'App::Yath::Command'; 10 | use Test2::Harness::Util::HashBase qw/<_command_info_hash/; 11 | 12 | use Test2::Harness::Util qw/open_file find_libraries/; 13 | use List::Util (); 14 | 15 | sub options {}; 16 | sub group { '' } 17 | sub summary { 'Show the list of commands' } 18 | 19 | sub description { 20 | return <<" EOT" 21 | This command provides a list of commands when called with no arguments. 22 | When given a command name as an argument it will print the help for that 23 | command. 24 | EOT 25 | } 26 | 27 | sub command_info_hash { 28 | my $self = shift; 29 | 30 | return $self->{+_COMMAND_INFO_HASH} if $self->{+_COMMAND_INFO_HASH}; 31 | 32 | my %commands; 33 | my $command_libs = find_libraries('App::Yath::Command::*'); 34 | for my $lib (sort keys %$command_libs) { 35 | my $ok = eval { require $command_libs->{$lib}; 1 }; 36 | unless ($ok) { 37 | warn "Failed to load command '$command_libs->{$lib}': $@"; 38 | next; 39 | } 40 | 41 | next if $lib->internal_only; 42 | my $name = $lib->name; 43 | my $group = $lib->group; 44 | $commands{$group}->{$name} = $lib->summary; 45 | } 46 | 47 | return $self->{+_COMMAND_INFO_HASH} = \%commands; 48 | } 49 | 50 | sub command_list { 51 | my $self = shift; 52 | 53 | my $command_hash = $self->command_info_hash(); 54 | my @commands = map keys %$_, values %$command_hash; 55 | return @commands; 56 | } 57 | 58 | sub run { 59 | my $self = shift; 60 | my $args = $self->{+ARGS}; 61 | 62 | return $self->command_help($args->[0]) if @$args; 63 | 64 | my $script = $self->settings->harness->script // $0; 65 | my $maxlen = List::Util::max(map length, $self->command_list); 66 | 67 | print "\nUsage: $script COMMAND [options]\n\nAvailable Commands:\n"; 68 | 69 | my $command_info_hash = $self->command_info_hash; 70 | for my $group (sort keys %$command_info_hash) { 71 | my $set = $command_info_hash->{$group}; 72 | 73 | printf(" %${maxlen}s: %s\n", $_, $set->{$_}) for sort keys %$set; 74 | print "\n"; 75 | } 76 | 77 | return 0; 78 | } 79 | 80 | sub command_help { 81 | my $self = shift; 82 | my ($command) = @_; 83 | 84 | require App::Yath; 85 | my $cmd_class = App::Yath->load_command($command); 86 | print $cmd_class->cli_help(settings => $self->{+SETTINGS}); 87 | 88 | return 0; 89 | } 90 | 91 | 1; 92 | 93 | __END__ 94 | 95 | =head1 POD IS AUTO-GENERATED 96 | 97 | -------------------------------------------------------------------------------- /t/integration/help.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | use App::Yath::Util qw/find_yath/; 8 | 9 | yath( 10 | command => 'help', 11 | args => [], 12 | exit => 0, 13 | test => sub { 14 | my $out = shift; 15 | 16 | like($out->{output}, qr{^Usage: .*yath COMMAND \[options\]$}m, "Found usage statement"); 17 | like($out->{output}, qr{^Available Commands:$}m, "available commands"); 18 | 19 | # Sample some essential commands 20 | like($out->{output}, qr{^\s+help: Show the list of commands$}m, "'help' command is listed"); 21 | like($out->{output}, qr{^\s+test: Run tests$}m, "'test' command is listed"); 22 | like($out->{output}, qr{^\s+start: Start the persistent test runner$}m, "'start' command is listed"); 23 | }, 24 | ); 25 | 26 | yath( 27 | command => 'help', 28 | args => ['help'], 29 | exit => 0, 30 | test => sub { 31 | my $out = shift; 32 | my $script = find_yath(); 33 | 34 | is($out->{output}, <<" EOT", "Got output for the help command"); 35 | help - Show the list of commands 36 | 37 | This command provides a list of commands when called with no arguments. 38 | When given a command name as an argument it will print the help for that 39 | command. 40 | 41 | Usage: $script help 42 | EOT 43 | }, 44 | ); 45 | 46 | yath( 47 | command => 'help', 48 | args => ['test'], 49 | exit => 0, 50 | test => sub { 51 | my $out = shift; 52 | 53 | like($out->{output}, qr{^test - Run tests$}m, "Found summary"); 54 | like($out->{output}, qr{^\[YATH OPTIONS\]$}m, "Found yath options"); 55 | like($out->{output}, qr{^ Developer$}m, "Found Developer category"); 56 | like($out->{output}, qr{^ Help and Debugging$}m, "Found help category"); 57 | like($out->{output}, qr{^ Plugins$}m, "Found plugin category"); 58 | like($out->{output}, qr{^\[COMMAND OPTIONS\]$}m, "Found command options"); 59 | like($out->{output}, qr{^ Display Options$}m, "Found display category"); 60 | like($out->{output}, qr{^ Formatter Options$}m, "Found formatter category"); 61 | like($out->{output}, qr{^ Logging Options$}m, "Found logging category"); 62 | like($out->{output}, qr{^ Run Options$}m, "Found run category"); 63 | like($out->{output}, qr{^ Runner Options$}m, "Found runner category"); 64 | like($out->{output}, qr{^ Workspace Options$}m, "Found workspace category"); 65 | }, 66 | ); 67 | 68 | done_testing; 69 | -------------------------------------------------------------------------------- /t/integration/projects.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Util qw/find_yath/; 7 | find_yath(); # cache result before we chdir 8 | 9 | use App::Yath::Tester qw/yath/; 10 | use Test2::Harness::Util::File::JSONL; 11 | 12 | use Test2::Harness::Util::JSON qw/decode_json/; 13 | 14 | my $dir = __FILE__; 15 | $dir =~ s{\.t$}{}g; 16 | $dir =~ s{^\./}{}; 17 | 18 | my $out; 19 | 20 | yath( 21 | command => 'projects', 22 | args => ['--ext=tx', '--', $dir], 23 | exit => 0, 24 | test => sub { 25 | my $out = shift; 26 | 27 | like($out->{output}, qr{PASSED .*foo.*t.*pass\.tx}, "Found pass.tx in foo project"); 28 | like($out->{output}, qr{PASSED .*bar.*t.*pass\.tx}, "Found pass.tx in bar project"); 29 | like($out->{output}, qr{PASSED .*baz.*t.*pass\.tx}, "Found pass.tx in baz project"); 30 | unlike($out->{output}, qr{fail\.txx}, "Did not run fail.txx"); 31 | }, 32 | ); 33 | 34 | yath( 35 | command => 'projects', 36 | args => ['--ext=tx', '--ext=txx', '--', $dir], 37 | exit => T(), 38 | test => sub { 39 | my $out = shift; 40 | like($out->{output}, qr{PASSED .*foo.*t.*pass\.tx}, "Found pass.tx in foo project"); 41 | like($out->{output}, qr{PASSED .*bar.*t.*pass\.tx}, "Found pass.tx in bar project"); 42 | like($out->{output}, qr{PASSED .*baz.*t.*pass\.tx}, "Found pass.tx in baz project"); 43 | like($out->{output}, qr{FAILED .*baz.*t.*fail\.txx}, "ran fail.txx"); 44 | }, 45 | ); 46 | 47 | chdir($dir); 48 | 49 | yath( 50 | command => 'projects', 51 | args => ['--ext=tx', '-v'], 52 | exit => 0, 53 | test => sub { 54 | my $out = shift; 55 | 56 | like($out->{output}, qr{PASSED .*foo.*t.*pass\.tx}, "Found pass.tx in foo project"); 57 | like($out->{output}, qr{PASSED .*bar.*t.*pass\.tx}, "Found pass.tx in bar project"); 58 | like($out->{output}, qr{PASSED .*baz.*t.*pass\.tx}, "Found pass.tx in baz project"); 59 | unlike($out->{output}, qr{fail\.txx}, "Did not run fail.txx"); 60 | }, 61 | ); 62 | 63 | yath( 64 | command => 'projects', 65 | args => ['--ext=tx', '--ext=txx'], 66 | exit => T(), 67 | test => sub { 68 | my $out = shift; 69 | 70 | like($out->{output}, qr{PASSED .*foo.*t.*pass\.tx}, "Found pass.tx in foo project"); 71 | like($out->{output}, qr{PASSED .*bar.*t.*pass\.tx}, "Found pass.tx in bar project"); 72 | like($out->{output}, qr{PASSED .*baz.*t.*pass\.tx}, "Found pass.tx in baz project"); 73 | like($out->{output}, qr{FAILED .*baz.*t.*fail\.txx}, "ran fail.txx"); 74 | }, 75 | ); 76 | 77 | done_testing; 78 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util/File/Stream.t: -------------------------------------------------------------------------------- 1 | use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File::Stream'; 2 | use File::Temp qw/tempfile/; 3 | # HARNESS-DURATION-SHORT 4 | 5 | use ok $CLASS; 6 | 7 | my ($wh, $filename) = tempfile("test-$$-XXXXXXXX", TMPDIR => 1); 8 | print $wh ""; 9 | close($wh); 10 | 11 | ok(my $one = $CLASS->new(name => $filename), "New instance"); 12 | $one->write("line1\n"); 13 | $one->write("line2\n"); 14 | $one->write("line3\n"); 15 | $one->write("line"); 16 | 17 | my $fh = $one->open_file('<'); 18 | is( 19 | [<$fh>], 20 | ["line1\n", "line2\n", "line3\n", "line"], 21 | "file written as expected" 22 | ); 23 | 24 | is($one->read_line, "line1\n", "got first line"); 25 | 26 | is( 27 | [$one->poll], 28 | [ 29 | "line2\n", 30 | "line3\n", 31 | ], 32 | "Got unseen completed lines, but not incomplete line" 33 | ); 34 | 35 | is($one->read_line, undef, "no new lines are ready"); 36 | 37 | is( 38 | [$one->read], 39 | [ 40 | "line1\n", 41 | "line2\n", 42 | "line3\n", 43 | ], 44 | "Read gets lines" 45 | ); 46 | 47 | $one->write("4\n"); 48 | $one->write("line5"); 49 | 50 | is( 51 | [$one->read], 52 | [ 53 | "line1\n", 54 | "line2\n", 55 | "line3\n", 56 | "line4\n", 57 | ], 58 | "Read sees the new lines" 59 | ); 60 | 61 | is([$one->poll], ["line4\n"], "Poll sees new line after a read"); 62 | 63 | $one->write("\nline6"); 64 | 65 | is($one->read_line, "line5\n", "read_line moves to the next line"); 66 | 67 | is($one->read_line, undef, "no new lines are ready"); 68 | is([$one->poll], [], "no new lines are ready"); 69 | 70 | $one->set_done(1); 71 | 72 | is([$one->poll], ["line6"], "got unterminated line after 'done' was set"); 73 | 74 | $one->reset; 75 | is( 76 | [$one->read], 77 | [ 78 | "line1\n", 79 | "line2\n", 80 | "line3\n", 81 | "line4\n", 82 | "line5\n", 83 | ], 84 | "read all lines but the last unterminated one" 85 | ); 86 | 87 | is( 88 | [$one->poll], 89 | [ 90 | "line1\n", 91 | "line2\n", 92 | "line3\n", 93 | "line4\n", 94 | "line5\n", 95 | ], 96 | "poll all lines but the last unterminated one" 97 | ); 98 | 99 | $one->set_done(1); 100 | is([$one->poll], ["line6"], "got unterminated line after 'done' was set"); 101 | 102 | $one = undef; 103 | 104 | $one = $CLASS->new(name => $filename); 105 | $one->seek(6); 106 | is( 107 | [$one->poll], 108 | [ 109 | "line2\n", 110 | "line3\n", 111 | "line4\n", 112 | "line5\n", 113 | ], 114 | "Was able to seek past the first item", 115 | ); 116 | 117 | unlink($filename); 118 | done_testing; 119 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util.t: -------------------------------------------------------------------------------- 1 | use Test2::Bundle::Extended -target => 'Test2::Harness::Util'; 2 | #BEGIN { skip_all 'TODO' } 3 | 4 | use ok $CLASS => ':ALL'; 5 | 6 | use File::Temp qw/tempfile tempdir/; 7 | 8 | imported_ok qw{ 9 | fqmod 10 | maybe_open_file 11 | maybe_read_file 12 | open_file 13 | read_file 14 | write_file 15 | write_file_atomic 16 | 17 | is_same_file 18 | }; 19 | 20 | my ($line) = split /\n/, read_file(__FILE__), 2; 21 | like( 22 | $line, 23 | q{use Test2::Bundle::Extended -target => 'Test2::Harness::Util';}, 24 | "Read file (only checking first line)" 25 | ); 26 | 27 | like( 28 | dies { read_file('/fake/file/that/must/not/exist cause I say so') }, 29 | qr{^\QCould not open file '/fake/file/that/must/not/exist cause I say so' (<)\E}, 30 | "Exception thrown when read_file used on non-existing file" 31 | ); 32 | 33 | is( 34 | maybe_read_file(__FILE__), 35 | read_file(__FILE__), 36 | "maybe_read_file reads file when it exists" 37 | ); 38 | 39 | is( 40 | maybe_read_file('/fake/file/that/must/not/exist cause I say so'), 41 | undef, 42 | "maybe_read_file is undef when file does not exist" 43 | ); 44 | 45 | ok(my $fh = open_file(__FILE__), "opened file"); 46 | ok($line = <$fh>, "Can read from file, default mode is 'read'"); 47 | 48 | if (-e '/dev/null') { 49 | ok(my $null = open_file('/dev/null', '>'), "opened /dev/null for writing"); 50 | ok((print $null "xxx\n"), "printed to /dev/null"); 51 | 52 | is( 53 | [write_file('/dev/null', "AAA", "BBB")], 54 | ["AAA", "BBB"], 55 | "wrote and returned content (/dev/null)" 56 | ); 57 | } 58 | 59 | is( 60 | maybe_open_file('/fake/file/that/must/not/exist cause I say so'), 61 | undef, 62 | "maybe_open_file is undef when file does not exist" 63 | ); 64 | 65 | is(fqmod('Foo::Bar', 'Baz'), 'Foo::Bar::Baz', "fqmod on postfix"); 66 | is(fqmod('Foo::Bar', 'Baz::Bat'), 'Foo::Bar::Baz::Bat', "fqmod on longer postfix"); 67 | is(fqmod('Foo::Bar', '+Baz'), 'Baz', "fqmod on fq"); 68 | is(fqmod('Foo::Bar', '+Baz::Bat'), 'Baz::Bat', "fqmod on longer fq"); 69 | 70 | my $tmp = tempdir(CLEANUP => 1, TMPDIR => 1); 71 | write_file_atomic(File::Spec->canonpath("$tmp/xxx"), "data"); 72 | $fh = open_file(File::Spec->canonpath("$tmp/xxx"), '<'); 73 | is(<$fh>, "data", "read data from file"); 74 | 75 | open($fh, '>', "$tmp/foo"); 76 | print $fh "\n"; 77 | close($fh); 78 | 79 | open($fh, '>', "$tmp/bar"); 80 | print $fh "\n"; 81 | close($fh); 82 | 83 | link("$tmp/foo", "$tmp/foo2") or die "Could not create link: $!"; 84 | symlink("$tmp/foo", "$tmp/foo3") or die "Could not create link: $!"; 85 | 86 | ok(is_same_file("$tmp/foo", "$tmp/foo"), "Matching filenames"); 87 | ok(is_same_file("$tmp/foo", "$tmp/foo2"), "hard link"); 88 | ok(is_same_file("$tmp/foo", "$tmp/foo3"), "soft link"); 89 | ok(!is_same_file("$tmp/foo", "$tmp/bar"), "Different files"); 90 | 91 | done_testing; 92 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | # This file is generated by Dist::Zilla::Plugin::CPANFile v6.032 2 | # Do not edit this file directly. To change prereqs, edit the `dist.ini` file. 3 | 4 | requires "Carp" => "0"; 5 | requires "Config" => "0"; 6 | requires "Cwd" => "0"; 7 | requires "Data::Dumper" => "0"; 8 | requires "Data::UUID" => "0"; 9 | requires "Exporter" => "0"; 10 | requires "Fcntl" => "0"; 11 | requires "File::Find" => "0"; 12 | requires "File::Path" => "2.11"; 13 | requires "File::Spec" => "0"; 14 | requires "File::Temp" => "0"; 15 | requires "Filter::Util::Call" => "0"; 16 | requires "IO::Compress::Bzip2" => "0"; 17 | requires "IO::Compress::Gzip" => "0"; 18 | requires "IO::Handle" => "1.27"; 19 | requires "IO::Uncompress::Bunzip2" => "0"; 20 | requires "IO::Uncompress::Gunzip" => "0"; 21 | requires "IPC::Cmd" => "0"; 22 | requires "Importer" => "0.025"; 23 | requires "JSON::PP" => "0"; 24 | requires "List::Util" => "1.44"; 25 | requires "Long::Jump" => "0.000001"; 26 | requires "POSIX" => "0"; 27 | requires "Scalar::Util" => "0"; 28 | requires "Scope::Guard" => "0"; 29 | requires "Symbol" => "0"; 30 | requires "Sys::Hostname" => "0"; 31 | requires "Term::Table" => "0.015"; 32 | requires "Test2" => "1.302170"; 33 | requires "Test2::API" => "1.302170"; 34 | requires "Test2::Bundle::Extended" => "0.000127"; 35 | requires "Test2::Event" => "1.302170"; 36 | requires "Test2::Event::V2" => "1.302170"; 37 | requires "Test2::Formatter" => "1.302170"; 38 | requires "Test2::Plugin::MemUsage" => "0.002003"; 39 | requires "Test2::Plugin::UUID" => "0.002001"; 40 | requires "Test2::Tools::AsyncSubtest" => "0.000127"; 41 | requires "Test2::Tools::Subtest" => "0.000127"; 42 | requires "Test2::Util" => "1.302170"; 43 | requires "Test2::Util::Term" => "0.000127"; 44 | requires "Test2::V0" => "0.000127"; 45 | requires "Test::Builder" => "1.302170"; 46 | requires "Test::Builder::Formatter" => "1.302170"; 47 | requires "Test::More" => "1.302170"; 48 | requires "Text::ParseWords" => "0"; 49 | requires "Time::HiRes" => "0"; 50 | requires "YAML::Tiny" => "0"; 51 | requires "base" => "0"; 52 | requires "constant" => "0"; 53 | requires "goto::file" => "0.005"; 54 | requires "parent" => "0"; 55 | requires "perl" => "5.010000"; 56 | suggests "Cpanel::JSON::XS" => "0"; 57 | suggests "Email::Stuffer" => "0.016"; 58 | suggests "HTTP::Tiny" => "0.070"; 59 | suggests "HTTP::Tiny::Multipart" => "0.08"; 60 | suggests "IO::Pager" => "1.00"; 61 | suggests "JSON::MaybeXS" => "0"; 62 | suggests "Term::ANSIColor" => "4.03"; 63 | suggests "Test2::Plugin::Cover" => "0.000025"; 64 | suggests "Test2::Plugin::DBIProfile" => "0.002002"; 65 | suggests "Test2::Plugin::IOEvents" => "0.001001"; 66 | suggests "Win32::Console::ANSI" => "0"; 67 | 68 | on 'test' => sub { 69 | requires "File::Copy" => "0"; 70 | }; 71 | 72 | on 'configure' => sub { 73 | requires "ExtUtils::MakeMaker" => "0"; 74 | }; 75 | 76 | on 'develop' => sub { 77 | requires "Test2::Require::Module" => "0.000127"; 78 | requires "Test::Pod" => "1.41"; 79 | requires "Test::Spelling" => "0.12"; 80 | }; 81 | -------------------------------------------------------------------------------- /lib/App/Yath/Command/watch.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Command::watch; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Time::HiRes qw/sleep/; 8 | 9 | use Test2::Harness::Util::File::JSON; 10 | 11 | use App::Yath::Util qw/find_pfile/; 12 | use Test2::Harness::Util qw/open_file/; 13 | 14 | use parent 'App::Yath::Command'; 15 | use Test2::Harness::Util::HashBase; 16 | 17 | sub group { 'persist' } 18 | 19 | sub summary { "Monitor the persistent test runner" } 20 | sub cli_args { "" } 21 | 22 | sub description { 23 | return <<" EOT"; 24 | This command will tail the logs from a persistent instance of yath. STDOUT and 25 | STDERR will be printed as seen, so may not be in proper order. 26 | EOT 27 | } 28 | 29 | sub run { 30 | my $self = shift; 31 | 32 | my $args = $self->args; 33 | shift @$args if @$args && $args->[0] eq '--'; 34 | my $stop; 35 | $stop = 1 if @$args && $args->[0] eq 'STOP'; 36 | 37 | my $pfile = find_pfile($self->settings, no_fatal => 1) 38 | or die "No persistent harness was found for the current path.\n"; 39 | 40 | print "\nFound: $pfile\n"; 41 | my $data = Test2::Harness::Util::File::JSON->new(name => $pfile)->read(); 42 | print " PID: $data->{pid}\n"; 43 | print " Dir: $data->{dir}\n"; 44 | print "\n"; 45 | 46 | my $err_f = File::Spec->catfile($data->{dir}, 'error.log'); 47 | my $out_f = File::Spec->catfile($data->{dir}, 'output.log'); 48 | 49 | my $err_fh = open_file($err_f, '<'); 50 | my $out_fh = open_file($out_f, '<'); 51 | 52 | my $auxdir = File::Spec->catdir($data->{dir}, 'aux_logs'); 53 | my %aux; 54 | 55 | while (1) { 56 | my $count = 0; 57 | while (my $line = <$out_fh>) { 58 | $count++; 59 | print STDOUT $line; 60 | } 61 | while (my $line = <$err_fh>) { 62 | $count++; 63 | print STDERR $line; 64 | } 65 | 66 | if (-d $auxdir) { 67 | opendir(my $dh, $auxdir) or die "Could not open auxdir: $!"; 68 | for my $file (readdir($dh)) { 69 | next if $aux{$file}; 70 | next unless $file =~ m/\.log$/; 71 | my $full = File::Spec->catfile($auxdir, $file); 72 | next unless -f $full; 73 | $aux{$file} = open_file($full, '<'); 74 | $count++; 75 | } 76 | } 77 | 78 | for my $file (sort keys %aux) { 79 | my $fh = $aux{$file}; 80 | my $ofh = $file =~ m/STDERR/ ? \*STDERR : \*STDOUT; 81 | while (my $line = <$fh>) { 82 | print $ofh $line; 83 | } 84 | } 85 | 86 | next if $count; 87 | last if $stop; 88 | last unless -f $pfile; 89 | sleep 0.02; 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | 96 | 1; 97 | 98 | __END__ 99 | 100 | =head1 POD IS AUTO-GENERATED 101 | 102 | -------------------------------------------------------------------------------- /lib/App/Yath/Converting.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Converting; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | 1; 8 | 9 | __END__ 10 | 11 | =pod 12 | 13 | =encoding UTF-8 14 | 15 | =head1 NAME 16 | 17 | App::Yath::Converting - Things you may need to change in your tests before you can use yath. 18 | 19 | =head1 NON-TAP FORMATTER 20 | 21 | By default yath tells any L or L tests to use 22 | L instead of L. This is done 23 | in order to make sure as much data as possible makes it to yath, TAP is a lossy 24 | formater by comparison. 25 | 26 | This is not normally a problem, but tests that do strange things with 27 | STDERR/STDOUT, or try to intercept output from the regular TAP formatter can 28 | have issues with this. 29 | 30 | =head2 SOLUTIONS 31 | 32 | =head3 HARNESS-NO-STREAM 33 | 34 | You can add a harness directive to the top of offending tests that tell the 35 | harness those specific tests should still use the TAP formatter. 36 | 37 | #!/usr/bin/perl 38 | # HARNESS-NO-STREAM 39 | ... 40 | 41 | This directive can come after the C<#!> line, and after use statements, but 42 | must come BEFORE any empty lines or runtime statements. 43 | 44 | =head3 --no-stream 45 | 46 | You can run yath with the C<--no-stream> option, which will have tests default 47 | to TAP. This is not recommended as TAP is lossy. 48 | 49 | =head1 TESTS ARE RUN VIA FORK BY DEFAULT 50 | 51 | The default mode for yath is to preload a few things, then fork to spawn each 52 | test. This is a complicated procedure, and it uses L under the 53 | hood. Sometimes you have tests that simply will not work this way, or tests 54 | that verify specific libraries are not already loaded. 55 | 56 | =head2 SOLUTIONS 57 | 58 | =head3 HARNESS-NO-PRELOAD 59 | 60 | You can use this harness directive inside your tests to tell yath not to fork, 61 | but to instead launch a new perl process to run the test. 62 | 63 | #!/usr/bin/perl 64 | # HARNESS-NO-PRELOAD 65 | ... 66 | 67 | =head3 --no-fork 68 | 69 | =head3 --no-preload 70 | 71 | Both these options tell yath not to preload+fork, but to run ALL tests in new 72 | processes. This is slow, it is better to mark specific tests that have issues 73 | in preload mode. 74 | 75 | =head1 SOURCE 76 | 77 | The source code repository for Test2-Harness can be found at 78 | F. 79 | 80 | =head1 MAINTAINERS 81 | 82 | =over 4 83 | 84 | =item Chad Granum Eexodist@cpan.orgE 85 | 86 | =back 87 | 88 | =head1 AUTHORS 89 | 90 | =over 4 91 | 92 | =item Chad Granum Eexodist@cpan.orgE 93 | 94 | =back 95 | 96 | =head1 COPYRIGHT 97 | 98 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 99 | 100 | This program is free software; you can redistribute it and/or 101 | modify it under the same terms as Perl itself. 102 | 103 | See F 104 | 105 | =cut 106 | -------------------------------------------------------------------------------- /lib/Test2/Harness/IPC/Process.pm: -------------------------------------------------------------------------------- 1 | package Test2::Harness::IPC::Process; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Carp qw/croak/; 8 | 9 | use Test2::Harness::Util::HashBase qw{ 10 | {+CATEGORY} //= 'default' } 16 | 17 | sub set_pid { 18 | my $self = shift; 19 | my ($pid) = @_; 20 | 21 | croak "pid has already been set" if defined $self->{+PID}; 22 | 23 | $self->{+PID} = $pid; 24 | } 25 | 26 | sub set_exit { 27 | my $self = shift; 28 | my ($ipc, $exit, $time) = @_; 29 | 30 | croak "exit has already been set" if defined $self->{+EXIT}; 31 | 32 | $self->{+EXIT} = $exit; 33 | $self->{+EXIT_TIME} = $time; 34 | } 35 | 36 | sub spawn_params { 37 | my $self = shift; 38 | my $class = ref($self) || $self; 39 | 40 | croak "Process class '$class' does not implement 'spawn_params()'"; 41 | } 42 | 43 | 1; 44 | 45 | __END__ 46 | 47 | =pod 48 | 49 | =encoding UTF-8 50 | 51 | =head1 NAME 52 | 53 | Test2::Harness::IPC::Process - Base class for processes controlled by 54 | Test2::Harness::IPC. 55 | 56 | =head1 DESCRIPTION 57 | 58 | All processes controlled by L should subclass this one. 59 | 60 | =head1 ATTRIBUTES 61 | 62 | =over 4 63 | 64 | =item $int = $proc->exit 65 | 66 | Exit value, if set. Otherwise C. 67 | 68 | =item $stamp = $proc->exit_time 69 | 70 | Timestamp of the process exit, if set, otherwise C. 71 | 72 | =item $pid = $proc->pid 73 | 74 | Pid of the process, if it has been started. 75 | 76 | =item $cat = $proc->category 77 | 78 | Set at construction, C<'default'> if not provided. 79 | 80 | =back 81 | 82 | =head1 METHODS 83 | 84 | =over 4 85 | 86 | =item $opt->set_pid($pid) 87 | 88 | Set the process id. 89 | 90 | =item $opt->set_exit($ipc, $exit, $time) 91 | 92 | Set the process as complete. $exit should be the exit value. $time should be a 93 | timestamp. $ipc is an instance of L. 94 | 95 | =item $hashref = $opt->spawn_params() 96 | 97 | Used when spawning the process, args go to C from 98 | L. 99 | 100 | The base class throws an exception if this method is called. 101 | 102 | =back 103 | 104 | =head1 SOURCE 105 | 106 | The source code repository for Test2-Harness can be found at 107 | F. 108 | 109 | =head1 MAINTAINERS 110 | 111 | =over 4 112 | 113 | =item Chad Granum Eexodist@cpan.orgE 114 | 115 | =back 116 | 117 | =head1 AUTHORS 118 | 119 | =over 4 120 | 121 | =item Chad Granum Eexodist@cpan.orgE 122 | 123 | =back 124 | 125 | =head1 COPYRIGHT 126 | 127 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 128 | 129 | This program is free software; you can redistribute it and/or 130 | modify it under the same terms as Perl itself. 131 | 132 | See F 133 | 134 | =cut 135 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Util/File.t: -------------------------------------------------------------------------------- 1 | use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File'; 2 | # HARNESS-DURATION-SHORT 3 | 4 | use ok $CLASS; 5 | 6 | can_ok($CLASS, qw/name done set_done/); 7 | 8 | like( 9 | dies { $CLASS->new }, 10 | qr/'name' is a required attribute/, 11 | "Must provide the 'name' attribute" 12 | ); 13 | 14 | open(my $tmpfh, '<', __FILE__) or die "Could not open file: $!"; 15 | my $zed = $CLASS->new(name => __FILE__, fh => $tmpfh); 16 | is($zed->_init_fh, $tmpfh, "saved fh"); 17 | is($zed->fh->blocking, 0, "fh was set to non-blocking"); 18 | $zed = undef; 19 | 20 | my $one = $CLASS->new(name => __FILE__); 21 | my $two = $CLASS->new(name => '/some/super/fake/file/that must not exist'); 22 | ok($one->exists, "This file exists"); 23 | ok(!$two->exists, "The file does not exist"); 24 | 25 | is($one->decode('xxx'), 'xxx', "base class decode does nothing"); 26 | is($one->encode('xxx'), 'xxx', "base class encode does nothing"); 27 | 28 | ok(my $fh = $one->open_file, "opened file (for reading)"); 29 | ok(dies { $two->open_file }, "Cannot open file (for reading)"); 30 | 31 | my ($line) = split /\n/, $one->maybe_read, 2; 32 | like( 33 | $line, 34 | q{use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File';}, 35 | "Can read file (using maybe_read)" 36 | ); 37 | 38 | is( 39 | $two->maybe_read, 40 | undef, 41 | "maybe_read returns undef for non-existant file" 42 | ); 43 | 44 | ($line) = split /\n/, $one->read, 2; 45 | like( 46 | $line, 47 | q{use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File';}, 48 | "Can read file" 49 | ); 50 | 51 | ok(dies { $two->read }, "read() dies on missing file"); 52 | 53 | close($fh); 54 | 55 | ok($fh = $one->fh, "Can generate an FH"); 56 | is($one->fh, $fh, "FH is remembered"); 57 | is($fh->blocking, 0, "FH is non-blocking"); 58 | 59 | close($fh); 60 | 61 | is($two->fh, undef, "return undef for missing file"); 62 | 63 | $one->set_done(1); 64 | is($one->done, 1, "can set done"); 65 | $one->reset; 66 | ok(!$one->{_fh}, "removed fh"); 67 | ok(!$one->done, "cleared done flag"); 68 | 69 | $two->reset; 70 | is($two->read_line, undef, "cannot read lines from missing file"); 71 | 72 | is( 73 | $one->read_line, 74 | "use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File';\n", 75 | "Got first line" 76 | ); 77 | 78 | while(my $l = $one->read_line) { 1 } 79 | 80 | is($one->read_line, undef, "no line to read yet"); 81 | $one->set_done(1); 82 | 83 | is( 84 | $one->read_line, 85 | "This line MUST be here, and MUST not end with a newline.", 86 | "Got final line with no terminator" 87 | ); 88 | 89 | $one->reset; 90 | is( 91 | $one->read_line, 92 | "use Test2::Bundle::Extended -target => 'Test2::Harness::Util::File';\n", 93 | "Got first line again after reset" 94 | ); 95 | 96 | #TODO: write (it is atomic) 97 | 98 | done_testing; 99 | 100 | __END__ 101 | 102 | This line MUST be here, and MUST not end with a newline. -------------------------------------------------------------------------------- /lib/App/Yath/Plugin/SysInfo.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Plugin::SysInfo; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use Sys::Hostname qw/hostname/; 8 | use Test2::Util qw/CAN_THREAD CAN_REALLY_FORK CAN_FORK CAN_SIGSYS/; 9 | use Config qw/%Config/; 10 | 11 | use parent 'App::Yath::Plugin'; 12 | use Test2::Harness::Util::HashBase qw/-host_short_pattern/; 13 | 14 | sub inject_run_data { 15 | my $self = shift; 16 | my %params = @_; 17 | 18 | my $meta = $params{meta}; 19 | my $fields = $params{fields}; 20 | 21 | my %data = ( 22 | env => { 23 | user => $ENV{USER}, 24 | shell => $ENV{SHELL}, 25 | term => $ENV{TERM}, 26 | }, 27 | 28 | ipc => { 29 | can_fork => CAN_FORK(), 30 | can_really_fork => CAN_REALLY_FORK(), 31 | can_thread => CAN_THREAD(), 32 | can_sigsys => CAN_SIGSYS(), 33 | }, 34 | ); 35 | 36 | my ($short, $raw) = ('sys', 'system info'); 37 | 38 | if (my $hostname = hostname()) { 39 | $short = undef; 40 | $data{hostname} = $hostname; 41 | $raw = $hostname; 42 | 43 | if (my $pattern = $self->{+HOST_SHORT_PATTERN}) { 44 | if ($hostname =~ /($pattern)/) { 45 | $short = $1; 46 | } 47 | } 48 | 49 | unless ($short) { 50 | $short = $hostname; 51 | $short =~ s/\.[^\.]*$// while length($short) > 18 && $short =~ m/\./; 52 | } 53 | } 54 | 55 | my @fields = qw/uselongdouble use64bitall version use64bitint usemultiplicity osname useperlio useithreads archname/; 56 | @{$data{config}}{@fields} = @Config{@fields}; 57 | 58 | push @$fields => { 59 | name => 'sys', 60 | details => $short, 61 | raw => $raw, 62 | data => \%data, 63 | }; 64 | } 65 | 66 | sub TO_JSON { ref($_[0]) } 67 | 68 | 1; 69 | 70 | __END__ 71 | 72 | =pod 73 | 74 | =encoding UTF-8 75 | 76 | =head1 NAME 77 | 78 | App::Yath::Plugin::SysInfo - Plugin to attach system information to a run. 79 | 80 | =head1 DESCRIPTION 81 | 82 | This plugin attaches a lot of system information to the yath log. This is 83 | mainly useful if you intend to view the log in L. 84 | 85 | =head1 SOURCE 86 | 87 | The source code repository for Test2-Harness can be found at 88 | F. 89 | 90 | =head1 MAINTAINERS 91 | 92 | =over 4 93 | 94 | =item Chad Granum Eexodist@cpan.orgE 95 | 96 | =back 97 | 98 | =head1 AUTHORS 99 | 100 | =over 4 101 | 102 | =item Chad Granum Eexodist@cpan.orgE 103 | 104 | =back 105 | 106 | =head1 COPYRIGHT 107 | 108 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 109 | 110 | This program is free software; you can redistribute it and/or 111 | modify it under the same terms as Perl itself. 112 | 113 | See F 114 | 115 | =cut 116 | -------------------------------------------------------------------------------- /t/integration/persist.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | use Test2::Harness::Util::File::JSONL; 8 | 9 | use Test2::Harness::Util::JSON qw/decode_json/; 10 | 11 | skip_all "This test is not run under automated testing" 12 | if $ENV{AUTOMATED_TESTING}; 13 | 14 | my $dir = __FILE__; 15 | $dir =~ s{\.t$}{}g; 16 | $dir =~ s{^\./}{}; 17 | 18 | yath(command => 'start', exit => 0); 19 | 20 | yath( 21 | command => 'run', 22 | args => [$dir, '--ext=tx', '--ext=txx'], 23 | exit => T(), 24 | test => sub { 25 | my $out = shift; 26 | like($out->{output}, qr{FAILED.*fail\.tx}, "'fail.tx' was seen as a failure when reading the output"); 27 | like($out->{output}, qr{PASSED.*pass\.tx}, "'pass.tx' was not seen as a failure when reading the output"); 28 | }, 29 | ); 30 | 31 | 32 | yath( 33 | command => 'run', 34 | args => [$dir, '--ext=tx'], 35 | exit => 0, 36 | test => sub { 37 | my $out = shift; 38 | unlike($out->{output}, qr{fail\.tx}, "'fail.tx' was not seen when reading the output"); 39 | like($out->{output}, qr{PASSED.*pass\.tx}, "'pass.tx' was not seen as a failure when reading the output"); 40 | }, 41 | ); 42 | 43 | yath( 44 | command => 'which', 45 | exit => 0, 46 | test => sub { 47 | my $out = shift; 48 | like($out->{output}, qr/^\s*Found: .*yath-persist\.json$/m, "Found the persist file"); 49 | like($out->{output}, qr/^\s*PID: /m, "Found the PID"); 50 | like($out->{output}, qr/^\s*Dir: /m, "Found the Dir"); 51 | }, 52 | ); 53 | 54 | yath(command => 'reload', exit => 0); 55 | 56 | yath( 57 | command => 'watch', 58 | args => ['STOP'], 59 | exit => 0, 60 | test => sub { 61 | my $out = shift; 62 | like($out->{output}, qr{yath-nested-runner \(default\) Runner caught SIGHUP, reloading}, "Reloaded runner"); 63 | }, 64 | ); 65 | 66 | yath( 67 | command => 'run', 68 | args => [$dir, '--ext=txx'], 69 | exit => T(), 70 | test => sub { 71 | my $out = shift; 72 | 73 | like($out->{output}, qr{FAILED.*fail\.tx}, "'fail.tx' was seen as a failure when reading the output"); 74 | unlike($out->{output}, qr{pass\.tx}, "'pass.tx' was not seen when reading the output"); 75 | }, 76 | ); 77 | 78 | yath( 79 | command => 'run', 80 | args => [$dir, '-vvv'], 81 | exit => T(), 82 | test => sub { 83 | my $out = shift; 84 | 85 | like($out->{output}, qr/No tests were seen!/, "Got error message"); 86 | }, 87 | ); 88 | 89 | yath(command => 'stop', exit => 0); 90 | 91 | yath( 92 | command => 'which', 93 | exit => 0, 94 | test => sub { 95 | my $out = shift; 96 | like($out->{output}, qr/No persistent harness was found for the current path\./, "No active runner"); 97 | }, 98 | ); 99 | 100 | done_testing; 101 | -------------------------------------------------------------------------------- /t/unit/Test2/Harness/Runner/DepTracer.t: -------------------------------------------------------------------------------- 1 | use Test2::V0 -target => 'Test2::Harness::Runner::DepTracer'; 2 | # HARNESS-NO-PRELOAD 3 | 4 | BEGIN { skip_all 'TODO' } 5 | 6 | use ok $CLASS; 7 | 8 | unshift @INC => 't/lib'; 9 | 10 | subtest require_hook => sub { 11 | my $one = $CLASS->new; 12 | isa_ok($one, [$CLASS], "Made a new instance"); 13 | ok(!$one->real_require, "Did not find an existing require hook"); 14 | 15 | my $two = $CLASS->new; 16 | ref_is($one->my_require, $two->real_require, "Found the existing require hook"); 17 | 18 | require xxx; 19 | 20 | is($one->loaded, {}, "Nothing tracked yet"); 21 | 22 | $one->start; 23 | 24 | # use eval so we do not pre-bind the require 25 | eval qq(#line ${ \__LINE__ } "${ \__FILE__ }"\nrequire baz; 1) or die $@; 26 | 27 | is($one->loaded, {map { $_ => T } qw/baz.pm foo.pm bar.pm/}, "Loaded 3 modules"); 28 | 29 | is( 30 | $one->dep_map, { 31 | 'baz.pm' => [['main', 't/Test2/Harness/Runner/DepTracer.t']], 32 | 'foo.pm' => [['baz', 't/lib/baz.pm'], ['bar', 't/lib/bar.pm']], 33 | 'bar.pm' => [['baz', 't/lib/baz.pm']], 34 | }, 35 | "Built dep-map" 36 | ); 37 | 38 | $one->stop; 39 | 40 | eval "require Data::Dumper; 1" or die $@; 41 | 42 | is($one->loaded, {map { $_ => T } qw/baz.pm foo.pm bar.pm/}, "Did not track Data::Dumper"); 43 | 44 | $one->clear_loaded; 45 | $one->start; 46 | 47 | eval "use 5.8.9; 1" or die $@; 48 | 49 | is($one->loaded, {}, "Did not track from version import"); 50 | }; 51 | 52 | subtest inc_hook => sub { 53 | my $one = $CLASS->new; 54 | isa_ok($one, [$CLASS], "Made a new instance"); 55 | ok($one->real_require, "Did find an existing require hook"); 56 | 57 | my $two = $CLASS->new; 58 | ref_is($one->my_require, $two->real_require, "Found the existing require hook"); 59 | 60 | require xxx; 61 | 62 | is($one->loaded, {}, "Nothing tracked yet"); 63 | 64 | $one->start; 65 | 66 | # use eval so we do not pre-bind the require 67 | eval qq(#line ${ \__LINE__ } "${ \__FILE__ }"\nCORE::require('baz_core.pm'); 1) or die $@; 68 | 69 | is($one->loaded, {map { $_ => T } qw/baz_core.pm foo_core.pm bar_core.pm/}, "Loaded 3 modules"); 70 | 71 | is( 72 | $one->dep_map, { 73 | 'baz_core.pm' => [['main', 't/Test2/Harness/Runner/DepTracer.t']], 74 | # The @INC hook is limited, it can catch hidden loads for watching, 75 | # but it cannot trace deps when a thing is loaded more than once. 76 | 'foo_core.pm' => [['baz_core', 't/lib/baz_core.pm']], #, ['bar', 't/lib/bar_core.pm']], 77 | 'bar_core.pm' => [['baz_core', 't/lib/baz_core.pm']], 78 | }, 79 | "Built dep-map" 80 | ); 81 | 82 | $one->stop; 83 | 84 | eval "CORE::require('yyy.pm'); 1" or die $@; 85 | 86 | is($one->loaded, {map { $_ => T } qw/baz_core.pm foo_core.pm bar_core.pm/}, "Did not track yyy"); 87 | 88 | $one->clear_loaded; 89 | $one->start; 90 | 91 | eval "use 5.8.9; 1" or die $@; 92 | 93 | is($one->loaded, {}, "Did not track from version import"); 94 | }; 95 | 96 | 97 | done_testing; 98 | -------------------------------------------------------------------------------- /t/integration/resource.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use File::Temp qw/tempdir/; 4 | use File::Spec; 5 | 6 | use App::Yath::Tester qw/yath/; 7 | use Test2::Harness::Util::File::JSONL; 8 | 9 | use Test2::Harness::Util::JSON qw/decode_json/; 10 | 11 | my $dir = __FILE__; 12 | $dir =~ s{\.t$}{}g; 13 | $dir =~ s{^\./}{}; 14 | 15 | yath( 16 | command => 'test', 17 | args => [$dir, '--ext=tx', '-j4', "-D$dir", '-R+Resource'], 18 | log => 1, 19 | exit => 0, 20 | test => sub { 21 | my $out = shift; 22 | my $log = $out->{log}; 23 | 24 | my @events = $log->poll(); 25 | 26 | my %pids; 27 | my %msgs; 28 | for my $event (@events) { 29 | my $f = $event->{facet_data}; 30 | my $info = $f->{info} or next; 31 | for my $i (@$info) { 32 | next unless $i->{tag} eq 'INTERNAL'; 33 | if ($i->{details} =~ m/^(\S+) - (yath-\S+)$/) { 34 | $pids{$1} = $2; 35 | next; 36 | } 37 | 38 | next unless $i->{details} =~ m/^(\S+) - (?:(\S+): \S+ - (\d)|(.+))$/; 39 | my ($pid, $action, $res_id) = ($1, ($2 || $4), $3); 40 | 41 | $pid = $pids{$pid} // $pid; 42 | 43 | if ($res_id) { 44 | push @{$msgs{$pid}->{$res_id}} => $action; 45 | } 46 | else { 47 | push @{$msgs{$pid}->{$_}} => $action for keys %{$msgs{$pid}}; 48 | } 49 | } 50 | } 51 | 52 | is( 53 | $msgs{"yath-nested-runner"}, 54 | { 55 | 1 => [ 56 | 'Record', 57 | 'Release', 58 | 'Record', 59 | 'Release', 60 | 'RESOURCE CLEANUP', 61 | ], 62 | 2 => [ 63 | 'Record', 64 | 'Release', 65 | 'Record', 66 | 'Release', 67 | 'RESOURCE CLEANUP', 68 | ], 69 | }, 70 | "The nested runner saw the records and releases, and then cleaned up at the end." 71 | ); 72 | 73 | is( 74 | $msgs{'yath-nested-scheduler'}, 75 | { 76 | 1 => [ 77 | 'Assigned', 78 | 'Record', 79 | 'No Slots', 80 | 'Release', 81 | 'Assigned', 82 | 'Record', 83 | 'Release', 84 | ], 85 | 2 => [ 86 | 'Assigned', 87 | 'Record', 88 | 'No Slots', 89 | 'Release', 90 | 'Assigned', 91 | 'Record', 92 | 'Release', 93 | ], 94 | }, 95 | "The scheduler handled assigning slots, knew when it was out, then knew when more were ready", 96 | ); 97 | }, 98 | ); 99 | 100 | done_testing; 101 | 102 | 1; 103 | -------------------------------------------------------------------------------- /lib/App/Yath/Options/Workspace.pm: -------------------------------------------------------------------------------- 1 | package App::Yath::Options::Workspace; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | use File::Spec(); 8 | use File::Path qw/remove_tree/; 9 | use File::Temp qw/tempdir/; 10 | 11 | use Test2::Harness::Util qw/clean_path chmod_tmp/; 12 | 13 | use App::Yath::Options; 14 | 15 | option_group {prefix => 'workspace', category => "Workspace Options"} => sub { 16 | option tmp_dir => ( 17 | type => 's', 18 | short => 't', 19 | alt => ['tmpdir'], 20 | description => 'Use a specific temp directory (Default: use system temp dir)', 21 | env_vars => [qw/T2_HARNESS_TEMP_DIR YATH_TEMP_DIR TMPDIR TEMPDIR TMP_DIR TEMP_DIR/], 22 | default => sub { File::Spec->tmpdir }, 23 | ); 24 | 25 | option workdir => ( 26 | type => 's', 27 | short => 'w', 28 | description => 'Set the work directory (Default: new temp directory)', 29 | env_vars => [qw/T2_WORKDIR YATH_WORKDIR/], 30 | clear_env_vars => 1, 31 | normalize => \&clean_path, 32 | ); 33 | 34 | option clear => ( 35 | short => 'C', 36 | description => 'Clear the work directory if it is not already empty', 37 | ); 38 | 39 | post sub { 40 | my %params = @_; 41 | my $settings = $params{settings}; 42 | 43 | if (my $workdir = $settings->workspace->workdir) { 44 | if (-d $workdir) { 45 | remove_tree($workdir, {safe => 1, keep_root => 1}) if $settings->workspace->clear; 46 | } 47 | else { 48 | mkdir($workdir) or die "Could not create workdir: $!"; 49 | chmod_tmp($workdir); 50 | } 51 | 52 | return; 53 | } 54 | 55 | my $project = $settings->harness->project; 56 | my $template = join '-' => ( "yath", $$, "XXXXXX"); 57 | 58 | my $tmpdir = tempdir( 59 | $template, 60 | DIR => $settings->workspace->tmp_dir, 61 | CLEANUP => !($settings->debug->keep_dirs || $params{command}->always_keep_dir), 62 | ); 63 | chmod_tmp($tmpdir); 64 | 65 | $settings->workspace->field(workdir => $tmpdir); 66 | }; 67 | }; 68 | 69 | 1; 70 | 71 | =pod 72 | 73 | =encoding UTF-8 74 | 75 | =head1 NAME 76 | 77 | App::Yath::Options::Workspace - Options for specifying the yath work dir. 78 | 79 | =head1 DESCRIPTION 80 | 81 | Options regarding the yath working directory. 82 | 83 | =head1 PROVIDED OPTIONS POD IS AUTO-GENERATED 84 | 85 | =head1 SOURCE 86 | 87 | The source code repository for Test2-Harness can be found at 88 | F. 89 | 90 | =head1 MAINTAINERS 91 | 92 | =over 4 93 | 94 | =item Chad Granum Eexodist@cpan.orgE 95 | 96 | =back 97 | 98 | =head1 AUTHORS 99 | 100 | =over 4 101 | 102 | =item Chad Granum Eexodist@cpan.orgE 103 | 104 | =back 105 | 106 | =head1 COPYRIGHT 107 | 108 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 109 | 110 | This program is free software; you can redistribute it and/or 111 | modify it under the same terms as Perl itself. 112 | 113 | See F 114 | 115 | =cut 116 | -------------------------------------------------------------------------------- /lib/Test2/Formatter/QVF.pm: -------------------------------------------------------------------------------- 1 | package Test2::Formatter::QVF; 2 | use strict; 3 | use warnings; 4 | 5 | our $VERSION = '1.000162'; 6 | 7 | BEGIN { require Test2::Formatter::Test2; our @ISA = qw(Test2::Formatter::Test2) } 8 | 9 | use Test2::Util::HashBase qw{ 10 | -job_buffers 11 | -real_verbose 12 | }; 13 | 14 | sub init { 15 | my $self = shift; 16 | $self->SUPER::init(); 17 | 18 | $self->{+REAL_VERBOSE} = $self->{+VERBOSE}; 19 | 20 | $self->{+VERBOSE} ||= 100; 21 | } 22 | 23 | sub update_active_disp { 24 | my $self = shift; 25 | my ($f) = @_; 26 | 27 | return if $f && $f->{__RENDER__}->{update_active_disp}++; 28 | 29 | $self->SUPER::update_active_disp($f); 30 | } 31 | 32 | sub write { 33 | my ($self, $e, $num, $f) = @_; 34 | 35 | return $self->SUPER::write($e, $num, $f) if $self->{+REAL_VERBOSE}; 36 | 37 | $f ||= $e->facet_data; 38 | 39 | my $job_id = $f->{harness}->{job_id}; 40 | 41 | push @{$self->{+JOB_BUFFERS}->{$job_id}} => [$e, $num, $f] 42 | if $job_id; 43 | 44 | my $show = $self->update_active_disp($f); 45 | 46 | if ($f->{harness_job_end} || !$job_id) { 47 | $show = 1; 48 | 49 | my $buffer = delete $self->{+JOB_BUFFERS}->{$job_id}; 50 | 51 | if($f->{harness_job_end}->{fail}) { 52 | $self->SUPER::write(@{$_}) for @$buffer; 53 | } 54 | else { 55 | $f->{info} = [grep { $_->{tag} ne 'TIME' } @{$f->{info}}] if $f->{info}; 56 | $self->SUPER::write($e, $num, $f) 57 | } 58 | } 59 | 60 | $self->{+ECOUNT}++; 61 | 62 | return unless $self->{+TTY}; 63 | return unless $self->{+PROGRESS}; 64 | 65 | $show ||= 1 unless $self->{+ECOUNT} % 10; 66 | 67 | if ($show) { 68 | # Local is expensive! Only do it if we really need to. 69 | local($\, $,) = (undef, '') if $\ || $,; 70 | 71 | my $io = $self->{+IO}; 72 | if ($self->{+_BUFFERED}) { 73 | print $io "\r\e[K"; 74 | $self->{+_BUFFERED} = 0; 75 | } 76 | 77 | print $io $self->render_status($f); 78 | $self->{+_BUFFERED} = 1; 79 | } 80 | 81 | return; 82 | } 83 | 84 | 1; 85 | 86 | __END__ 87 | 88 | =pod 89 | 90 | =encoding UTF-8 91 | 92 | =head1 NAME 93 | 94 | Test2::Formatter::QVF - Test2 formatter that is [Q]uiet but [V]erbose on 95 | [F]ailure. 96 | 97 | =head1 DESCRIPTION 98 | 99 | This formatter is a subclass of L. This one will 100 | buffer all output from a test file and only show it to you if there is a 101 | failure. Most of the time it willonly show you the completion notifications for 102 | each test. 103 | 104 | =head1 SYNOPSIS 105 | 106 | $ yath test --qvf ... 107 | 108 | =head1 SOURCE 109 | 110 | The source code repository for Test2-Harness can be found at 111 | F. 112 | 113 | =head1 MAINTAINERS 114 | 115 | =over 4 116 | 117 | =item Chad Granum Eexodist@cpan.orgE 118 | 119 | =back 120 | 121 | =head1 AUTHORS 122 | 123 | =over 4 124 | 125 | =item Chad Granum Eexodist@cpan.orgE 126 | 127 | =back 128 | 129 | =head1 COPYRIGHT 130 | 131 | Copyright 2020 Chad Granum Eexodist7@gmail.comE. 132 | 133 | This program is free software; you can redistribute it and/or 134 | modify it under the same terms as Perl itself. 135 | 136 | See F 137 | 138 | =cut 139 | 140 | -------------------------------------------------------------------------------- /t/integration/plugin.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use App::Yath::Tester qw/yath/; 4 | use File::Temp qw/tempdir/; 5 | use Test2::Harness::Util::File::JSONL; 6 | 7 | use Test2::Harness::Util::JSON qw/decode_json/; 8 | 9 | my $dir = __FILE__; 10 | $dir =~ s{\.t$}{}g; 11 | $dir =~ s{^\./}{}; 12 | 13 | sub verify { 14 | my (@outputs) = @_; 15 | 16 | my $text = ''; 17 | for my $out (@outputs) { 18 | $text .= $out->{output}; 19 | } 20 | 21 | like($text, qr/TEST PLUGIN: Loaded Plugin/, "Yath loaded the plugin"); 22 | like($text, qr/TEST PLUGIN: duration_data/, "duration_data() was called"); 23 | 24 | like($text, qr/TEST PLUGIN: changed_files\(Test2::Harness::Settings\)/, "changed_files() was called"); 25 | like($text, qr/TEST PLUGIN: get_coverage_tests\(Test2::Harness::Settings, HASH\(5\)\)/, "get_coverage_tests() was called"); 26 | 27 | like($text, qr/TEST PLUGIN: munge_files/, "munge_files() was called"); 28 | like($text, qr/TEST PLUGIN: munge_search/, "munge_search() was called"); 29 | like($text, qr/TEST PLUGIN: inject_run_data/, "inject_run_data() was called"); 30 | like($text, qr/TEST PLUGIN: handle_event/, "handle_event() was called"); 31 | 32 | like($text, qr/TEST PLUGIN: claim_file .*test\.tx$/m, "claim_file(test.tx) was called"); 33 | like($text, qr/TEST PLUGIN: claim_file .*TestPlugin\.pm$/m, "claim_file(TestPlugin.pm) was called"); 34 | like($text, qr/TEST PLUGIN: setup Test2::Harness::Settings/, "setup() was called with settings"); 35 | like($text, qr/TEST PLUGIN: teardown Test2::Harness::Settings/, "teardown() was called with settings"); 36 | 37 | like($text, qr/\(TESTPLUG\)\s+STDERR WRITE$/m, "Got the STDERR write from the shellcall"); 38 | like($text, qr/\(TESTPLUG\)\s+STDOUT WRITE$/m, "Got the STDOUT write from the shellcall"); 39 | 40 | like( 41 | $text, 42 | qr/TEST PLUGIN: finish asserts_seen => 10, final_data => HASH, pass => 1, settings => Test2::Harness::Settings, tests_seen => 5/, 43 | "finish() was called with necessary args" 44 | ); 45 | 46 | is(@{[$text =~ m/TEST PLUGIN: setup/g]}, 1, "Only ran setup once"); 47 | is(@{[$text =~ m/TEST PLUGIN: teardown/g]}, 1, "Only ran teardown once"); 48 | is(@{[$text =~ m/TEST PLUGIN: finish/g]}, 1, "Only ran finish once"); 49 | 50 | if (ok($text =~ m/^FIELDS:(.*)$/m, "Found fields")) { 51 | my $data = decode_json($1); 52 | is( 53 | $data, 54 | [{ 55 | name => 'test_plugin', details => 'foo', raw => 'bar', data => 'baz', 56 | }], 57 | "Injected the run data" 58 | ); 59 | } 60 | 61 | my %rank = ( 62 | test => 1, 63 | c => 2, 64 | b => 3, 65 | a => 4, 66 | d => 5, 67 | ); 68 | 69 | my %jobs = reverse($text =~ m{job\s+(\d+)\s+.*\W(\w+)\.tx}g); 70 | is(\%jobs, \%rank, "Ran jobs in specified order"); 71 | } 72 | 73 | yath( 74 | command => 'test', 75 | args => [$dir, '--ext=tx', '-A', '--durations-threshold' => 1, '--no-plugins', '-pTestPlugin', '--changes-plugin', 'TestPlugin'], 76 | exit => 0, 77 | test => \&verify, 78 | ); 79 | 80 | unless ($ENV{AUTOMATED_TESTING} || $ENV{AUTHOR_TESTING}) { 81 | subtest persist => sub { 82 | verify( 83 | yath(command => 'start', args => ['--no-plugins', '-pTestPlugin'], exit => 0), 84 | yath(command => 'run', args => ['--no-plugins', '-pTestPlugin', '--changes-plugin', 'TestPlugin', exit => 0, $dir, '--ext=tx', '-A']), 85 | yath(command => 'stop', args => ['--no-plugins', '-pTestPlugin'], exit => 0), 86 | ); 87 | }; 88 | } 89 | 90 | done_testing; 91 | --------------------------------------------------------------------------------