+ [% IF runcommandlogProblem %]
+
+ [% IF runcommandlogProblem == "disabled-server" %]
+ This server does not enable Dynamic RunCommand support.
+ [% ELSIF runcommandlogProblem == "disabled-project" %]
+ This project does not enable Dynamic RunCommand support.
+ [% ELSIF runcommandlogProblem == "disabled-jobset" %]
+ This jobset does not enable Dynamic RunCommand support.
+ [% ELSE %]
+ Dynamic RunCommand is not enabled: [% runcommandlogProblem %].
+ [% END %]
+
+ [% END %]
[% FOREACH runcommandlog IN runcommandlogs %]
diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt
index dbd26dcc..61e3636f 100644
--- a/src/root/edit-jobset.tt
+++ b/src/root/edit-jobset.tt
@@ -157,6 +157,21 @@
+
+
+
+
+
Declarative spec file
diff --git a/src/root/jobset.tt b/src/root/jobset.tt
index 4fb52517..56abdb50 100644
--- a/src/root/jobset.tt
+++ b/src/root/jobset.tt
@@ -160,6 +160,10 @@
Scheduling shares:
[% jobset.schedulingshares %] [% IF totalShares %] ([% f = format("%.2f"); f(jobset.schedulingshares / totalShares * 100) %]% out of [% totalShares %] shares)[% END %]
+
+ Enable Dynamic RunCommand Hooks:
+ [% c.config.dynamicruncommand.enable ? project.enable_dynamic_run_command ? jobset.enable_dynamic_run_command ? "Yes" : "No (not enabled by jobset)" : "No (not enabled by project)" : "No (not enabled by server)" %]
+
[% IF emailNotification %]
Enable email notification:
diff --git a/src/root/project.tt b/src/root/project.tt
index 04661c82..5e8ec0c8 100644
--- a/src/root/project.tt
+++ b/src/root/project.tt
@@ -92,6 +92,10 @@
Enabled:
[% project.enabled ? "Yes" : "No" %]
+
+ Enable Dynamic RunCommand Hooks:
+ [% c.config.dynamicruncommand.enable ? project.enable_dynamic_run_command ? "Yes" : "No (not enabled by project)" : "No (not enabled by server)" %]
+
diff --git a/src/script/hydra-eval-jobset b/src/script/hydra-eval-jobset
index 8bafe07c..8804b2bb 100755
--- a/src/script/hydra-eval-jobset
+++ b/src/script/hydra-eval-jobset
@@ -619,7 +619,7 @@ sub checkJobsetWrapped {
} else {
# Update the jobset with the spec's inputs, and the continue
# evaluating the .jobsets jobset.
- updateDeclarativeJobset($db, $project, ".jobsets", $declSpec);
+ updateDeclarativeJobset($config, $db, $project, ".jobsets", $declSpec);
$jobset->discard_changes;
$inputInfo->{"declInput"} = [ $declInput ];
$inputInfo->{"projectName"} = [ fetchInput($plugins, $db, $project, $jobset, "projectName", "string", $project->name, 0) ];
diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql
index 26617789..eaae6da3 100644
--- a/src/sql/hydra.sql
+++ b/src/sql/hydra.sql
@@ -49,6 +49,7 @@ create table Projects (
declfile text, -- File containing declarative jobset specification
decltype text, -- Type of the input containing declarative jobset specification
declvalue text, -- Value of the input containing declarative jobset specification
+ enable_dynamic_run_command boolean not null default false,
foreign key (owner) references Users(userName) on update cascade
);
@@ -88,6 +89,7 @@ create table Jobsets (
startTime integer, -- if jobset is currently running
type integer not null default 0, -- 0 == legacy, 1 == flake
flake text,
+ enable_dynamic_run_command boolean not null default false,
constraint jobsets_schedulingshares_nonzero_check check (schedulingShares > 0),
constraint jobsets_type_known_check check (type = 0 or type = 1),
-- If the type is 0, then nixExprInput and nixExprPath should be non-null and other type-specific fields should be null
diff --git a/src/sql/upgrade-82.sql b/src/sql/upgrade-82.sql
new file mode 100644
index 00000000..a619caf3
--- /dev/null
+++ b/src/sql/upgrade-82.sql
@@ -0,0 +1,4 @@
+ALTER TABLE Jobsets
+ ADD COLUMN enable_dynamic_run_command boolean not null default false;
+ALTER TABLE Projects
+ ADD COLUMN enable_dynamic_run_command boolean not null default false;
diff --git a/t/Helper/AddBuilds/dynamic-disabled.t b/t/Helper/AddBuilds/dynamic-disabled.t
new file mode 100644
index 00000000..0507b03e
--- /dev/null
+++ b/t/Helper/AddBuilds/dynamic-disabled.t
@@ -0,0 +1,85 @@
+use strict;
+use warnings;
+use Setup;
+use Test2::V0;
+
+require Catalyst::Test;
+use HTTP::Request::Common qw(POST PUT GET DELETE);
+use JSON::MaybeXS qw(decode_json encode_json);
+use Hydra::Helper::AddBuilds qw(validateDeclarativeJobset);
+use Hydra::Helper::Nix qw(getHydraConfig);
+
+my $ctx = test_context();
+
+sub makeJobsetSpec {
+ my ($dynamic) = @_;
+
+ return {
+ enabled => 2,
+ enable_dynamic_run_command => $dynamic ? JSON::MaybeXS::true : undef,
+ visible => JSON::MaybeXS::true,
+ name => "job",
+ type => 1,
+ description => "test jobset",
+ flake => "github:nixos/nix",
+ checkinterval => 0,
+ schedulingshares => 100,
+ keepnr => 3
+ };
+};
+
+subtest "validate declarative jobset with dynamic RunCommand disabled by server" => sub {
+ my $config = getHydraConfig();
+
+ subtest "project enabled dynamic runcommand, declarative jobset enabled dynamic runcommand" => sub {
+ like(
+ dies {
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 1 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::true),
+ ),
+ },
+ qr/Dynamic RunCommand is not enabled/,
+ );
+ };
+
+ subtest "project enabled dynamic runcommand, declarative jobset disabled dynamic runcommand" => sub {
+ ok(
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 1 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::false)
+ ),
+ );
+ };
+
+ subtest "project disabled dynamic runcommand, declarative jobset enabled dynamic runcommand" => sub {
+ like(
+ dies {
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 0 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::true),
+ ),
+ },
+ qr/Dynamic RunCommand is not enabled/,
+ );
+ };
+
+ subtest "project disabled dynamic runcommand, declarative jobset disabled dynamic runcommand" => sub {
+ ok(
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 0 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::false)
+ ),
+ );
+ };
+};
+
+done_testing;
diff --git a/t/Helper/AddBuilds/dynamic-enabled.t b/t/Helper/AddBuilds/dynamic-enabled.t
new file mode 100644
index 00000000..d2f5a386
--- /dev/null
+++ b/t/Helper/AddBuilds/dynamic-enabled.t
@@ -0,0 +1,88 @@
+use strict;
+use warnings;
+use Setup;
+use Test2::V0;
+
+require Catalyst::Test;
+use HTTP::Request::Common qw(POST PUT GET DELETE);
+use JSON::MaybeXS qw(decode_json encode_json);
+use Hydra::Helper::AddBuilds qw(validateDeclarativeJobset);
+use Hydra::Helper::Nix qw(getHydraConfig);
+
+my $ctx = test_context(
+ hydra_config => q|
+
+ enable = 1
+
+ |
+);
+
+sub makeJobsetSpec {
+ my ($dynamic) = @_;
+
+ return {
+ enabled => 2,
+ enable_dynamic_run_command => $dynamic ? JSON::MaybeXS::true : undef,
+ visible => JSON::MaybeXS::true,
+ name => "job",
+ type => 1,
+ description => "test jobset",
+ flake => "github:nixos/nix",
+ checkinterval => 0,
+ schedulingshares => 100,
+ keepnr => 3
+ };
+};
+
+subtest "validate declarative jobset with dynamic RunCommand enabled by server" => sub {
+ my $config = getHydraConfig();
+
+ subtest "project enabled dynamic runcommand, declarative jobset enabled dynamic runcommand" => sub {
+ ok(
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 1 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::true)
+ ),
+ );
+ };
+
+ subtest "project enabled dynamic runcommand, declarative jobset disabled dynamic runcommand" => sub {
+ ok(
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 1 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::false)
+ ),
+ );
+ };
+
+ subtest "project disabled dynamic runcommand, declarative jobset enabled dynamic runcommand" => sub {
+ like(
+ dies {
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 0 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::true),
+ ),
+ },
+ qr/Dynamic RunCommand is not enabled/,
+ );
+ };
+
+ subtest "project disabled dynamic runcommand, declarative jobset disabled dynamic runcommand" => sub {
+ ok(
+ validateDeclarativeJobset(
+ $config,
+ { enable_dynamic_run_command => 0 },
+ "test-jobset",
+ makeJobsetSpec(JSON::MaybeXS::false)
+ ),
+ );
+ };
+};
+
+done_testing;
diff --git a/t/Hydra/Controller/Jobset/http.t b/t/Hydra/Controller/Jobset/http.t
index 32b3a681..4e53949d 100644
--- a/t/Hydra/Controller/Jobset/http.t
+++ b/t/Hydra/Controller/Jobset/http.t
@@ -73,6 +73,7 @@ subtest 'Read newly-created jobset "job"' => sub {
emailoverride => "",
enabled => 2,
enableemail => JSON::MaybeXS::false,
+ enable_dynamic_run_command => JSON::MaybeXS::false,
errortime => undef,
errormsg => "",
fetcherrormsg => "",
@@ -131,6 +132,7 @@ subtest 'Update jobset "job" to legacy type' => sub {
emailoverride => "",
enabled => 3,
enableemail => JSON::MaybeXS::false,
+ enable_dynamic_run_command => JSON::MaybeXS::false,
errortime => undef,
errormsg => "",
fetcherrormsg => "",
diff --git a/t/Hydra/Controller/projects.t b/t/Hydra/Controller/projects.t
index df1290aa..130724cf 100644
--- a/t/Hydra/Controller/projects.t
+++ b/t/Hydra/Controller/projects.t
@@ -46,6 +46,7 @@ subtest "Read project 'tests'" => sub {
description => "",
displayname => "Tests",
enabled => JSON::MaybeXS::true,
+ enable_dynamic_run_command => JSON::MaybeXS::false,
hidden => JSON::MaybeXS::false,
homepage => "",
jobsets => [],
@@ -85,6 +86,7 @@ subtest "Transitioning from declarative project to normal" => sub {
description => "",
displayname => "Tests",
enabled => JSON::MaybeXS::true,
+ enable_dynamic_run_command => JSON::MaybeXS::false,
hidden => JSON::MaybeXS::false,
homepage => "",
jobsets => [".jobsets"],
@@ -128,6 +130,7 @@ subtest "Transitioning from declarative project to normal" => sub {
description => "",
displayname => "Tests",
enabled => JSON::MaybeXS::true,
+ enable_dynamic_run_command => JSON::MaybeXS::false,
hidden => JSON::MaybeXS::false,
homepage => "",
jobsets => [],
diff --git a/t/Hydra/Plugin/RunCommand/dynamic-disabled.t b/t/Hydra/Plugin/RunCommand/dynamic-disabled.t
new file mode 100644
index 00000000..ad2e9a4b
--- /dev/null
+++ b/t/Hydra/Plugin/RunCommand/dynamic-disabled.t
@@ -0,0 +1,110 @@
+use strict;
+use warnings;
+use Setup;
+use Test2::V0;
+
+require Catalyst::Test;
+use HTTP::Request::Common qw(POST PUT GET DELETE);
+use JSON::MaybeXS qw(decode_json encode_json);
+
+my $ctx = test_context();
+Catalyst::Test->import('Hydra');
+
+# Create a user to log in to
+my $user = $ctx->db->resultset('Users')->create({ username => 'alice', emailaddress => 'root@invalid.org', password => '!' });
+$user->setPassword('foobar');
+$user->userroles->update_or_create({ role => 'admin' });
+
+subtest "can't enable dynamic RunCommand when disabled by server" => sub {
+ my $builds = $ctx->makeAndEvaluateJobset(
+ expression => "runcommand-dynamic.nix",
+ build => 1
+ );
+
+ my $build = $builds->{"runCommandHook.example"};
+ my $project = $build->project;
+ my $project_name = $project->name;
+ my $jobset = $build->jobset;
+ my $jobset_name = $jobset->name;
+
+ is($project->enable_dynamic_run_command, 0, "dynamic RunCommand is disabled on projects by default");
+ is($jobset->enable_dynamic_run_command, 0, "dynamic RunCommand is disabled on jobsets by default");
+
+ my $req = request(POST '/login',
+ Referer => 'http://localhost/',
+ Content => {
+ username => 'alice',
+ password => 'foobar'
+ }
+ );
+ is($req->code, 302, "logged in successfully");
+ my $cookie = $req->header("set-cookie");
+
+ subtest "can't enable dynamic RunCommand on project" => sub {
+ my $projectresponse = request(GET "/project/$project_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+
+ my $projectjson = decode_json($projectresponse->content);
+ $projectjson->{enable_dynamic_run_command} = 1;
+
+ my $projectupdate = request(PUT "/project/$project_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ Content => encode_json($projectjson)
+ );
+
+ $projectresponse = request(GET "/project/$project_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+ $projectjson = decode_json($projectresponse->content);
+
+ is($projectupdate->code, 400);
+ like(
+ $projectupdate->content,
+ qr/Dynamic RunCommand is not/,
+ "failed to change enable_dynamic_run_command, not any other error"
+ );
+ is($projectjson->{enable_dynamic_run_command}, JSON::MaybeXS::false);
+ };
+
+ subtest "can't enable dynamic RunCommand on jobset" => sub {
+ my $jobsetresponse = request(GET "/jobset/$project_name/$jobset_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+
+ my $jobsetjson = decode_json($jobsetresponse->content);
+ $jobsetjson->{enable_dynamic_run_command} = 1;
+
+ my $jobsetupdate = request(PUT "/jobset/$project_name/$jobset_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ Content => encode_json($jobsetjson)
+ );
+
+ $jobsetresponse = request(GET "/jobset/$project_name/$jobset_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+ $jobsetjson = decode_json($jobsetresponse->content);
+
+ is($jobsetupdate->code, 400);
+ like(
+ $jobsetupdate->content,
+ qr/Dynamic RunCommand is not/,
+ "failed to change enable_dynamic_run_command, not any other error"
+ );
+ is($jobsetjson->{enable_dynamic_run_command}, JSON::MaybeXS::false);
+ };
+};
+
+done_testing;
diff --git a/t/Hydra/Plugin/RunCommand/dynamic-enabled.t b/t/Hydra/Plugin/RunCommand/dynamic-enabled.t
new file mode 100644
index 00000000..68c6d593
--- /dev/null
+++ b/t/Hydra/Plugin/RunCommand/dynamic-enabled.t
@@ -0,0 +1,106 @@
+use strict;
+use warnings;
+use Setup;
+use Test2::V0;
+
+require Catalyst::Test;
+use HTTP::Request::Common qw(POST PUT GET DELETE);
+use JSON::MaybeXS qw(decode_json encode_json);
+
+my $ctx = test_context(
+ hydra_config => q|
+
+ enable = 1
+
+ |
+);
+Catalyst::Test->import('Hydra');
+
+# Create a user to log in to
+my $user = $ctx->db->resultset('Users')->create({ username => 'alice', emailaddress => 'root@invalid.org', password => '!' });
+$user->setPassword('foobar');
+$user->userroles->update_or_create({ role => 'admin' });
+
+subtest "can enable dynamic RunCommand when enabled by server" => sub {
+ my $builds = $ctx->makeAndEvaluateJobset(
+ expression => "runcommand-dynamic.nix",
+ build => 1
+ );
+
+ my $build = $builds->{"runCommandHook.example"};
+ my $project = $build->project;
+ my $project_name = $project->name;
+ my $jobset = $build->jobset;
+ my $jobset_name = $jobset->name;
+
+ is($project->enable_dynamic_run_command, 0, "dynamic RunCommand is disabled on projects by default");
+ is($jobset->enable_dynamic_run_command, 0, "dynamic RunCommand is disabled on jobsets by default");
+
+ my $req = request(POST '/login',
+ Referer => 'http://localhost/',
+ Content => {
+ username => 'alice',
+ password => 'foobar'
+ }
+ );
+ is($req->code, 302, "logged in successfully");
+ my $cookie = $req->header("set-cookie");
+
+ subtest "can enable dynamic RunCommand on project" => sub {
+ my $projectresponse = request(GET "/project/$project_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+
+ my $projectjson = decode_json($projectresponse->content);
+ $projectjson->{enable_dynamic_run_command} = 1;
+
+ my $projectupdate = request(PUT "/project/$project_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ Content => encode_json($projectjson)
+ );
+
+ $projectresponse = request(GET "/project/$project_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+ $projectjson = decode_json($projectresponse->content);
+
+ is($projectupdate->code, 200);
+ is($projectjson->{enable_dynamic_run_command}, JSON::MaybeXS::true);
+ };
+
+ subtest "can enable dynamic RunCommand on jobset" => sub {
+ my $jobsetresponse = request(GET "/jobset/$project_name/$jobset_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+
+ my $jobsetjson = decode_json($jobsetresponse->content);
+ $jobsetjson->{enable_dynamic_run_command} = 1;
+
+ my $jobsetupdate = request(PUT "/jobset/$project_name/$jobset_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ Content => encode_json($jobsetjson)
+ );
+
+ $jobsetresponse = request(GET "/jobset/$project_name/$jobset_name",
+ Accept => 'application/json',
+ Content_Type => 'application/json',
+ Cookie => $cookie,
+ );
+ $jobsetjson = decode_json($jobsetresponse->content);
+
+ is($jobsetupdate->code, 200);
+ is($jobsetjson->{enable_dynamic_run_command}, JSON::MaybeXS::true);
+ };
+};
+
+done_testing;
diff --git a/t/Hydra/Plugin/RunCommand/fanout.t b/t/Hydra/Plugin/RunCommand/fanout.t
new file mode 100644
index 00000000..328824f9
--- /dev/null
+++ b/t/Hydra/Plugin/RunCommand/fanout.t
@@ -0,0 +1,233 @@
+use strict;
+use warnings;
+use Setup;
+use Test2::V0;
+use Hydra::Plugin::RunCommand;
+
+my $ctx = test_context();
+
+my $builds = $ctx->makeAndEvaluateJobset(
+ expression => "runcommand-dynamic.nix",
+ build => 1
+);
+
+my $build = $builds->{"runCommandHook.example"};
+
+# Enable dynamic runcommand on the project and jobset
+$build->project->update({enable_dynamic_run_command => 1});
+$build->jobset->update({enable_dynamic_run_command => 1});
+
+is($build->job, "runCommandHook.example", "The only job should be runCommandHook.example");
+is($build->finished, 1, "Build should be finished.");
+is($build->buildstatus, 0, "Build should have buildstatus 0.");
+
+subtest "fanoutToCommands" => sub {
+ my $config = {
+ runcommand => [
+ {
+ job => "",
+ command => "foo"
+ },
+ {
+ job => "*:*:*",
+ command => "bar"
+ },
+ {
+ job => "tests:basic:nomatch",
+ command => "baz"
+ }
+ ]
+ };
+
+ is(
+ Hydra::Plugin::RunCommand::fanoutToCommands(
+ $config,
+ "buildFinished",
+ $build
+ ),
+ [
+ {
+ matcher => "",
+ command => "foo"
+ },
+ {
+ matcher => "*:*:*",
+ command => "bar"
+ }
+ ],
+ "fanoutToCommands returns a command per matching job"
+ );
+};
+
+subtest "fanoutToCommandsWithDynamicRunCommandSupport" => sub {
+ like(
+ $build->buildoutputs->find({name => "out"})->path,
+ qr/my-build-product$/,
+ "The way we find the out path is reasonable"
+ );
+
+ my $config = {
+ dynamicruncommand => { enable => 1 },
+ runcommand => [
+ {
+ job => "*:*:*",
+ command => "baz"
+ }
+ ]
+ };
+
+ is(
+ Hydra::Plugin::RunCommand::fanoutToCommands(
+ $config,
+ "buildFinished",
+ $build
+ ),
+ [
+ {
+ matcher => "*:*:*",
+ command => "baz"
+ },
+ {
+ matcher => "DynamicRunCommand(runCommandHook.example)",
+ command => $build->buildoutputs->find({name => "out"})->path
+ }
+ ],
+ "fanoutToCommands returns a command per matching job"
+ );
+};
+
+subtest "isBuildEligibleForDynamicRunCommand" => sub {
+ subtest "Non-matches based on name alone ..." => sub {
+ my $build = $builds->{"foo-bar-baz"};
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build),
+ 0,
+ "The job name does not match"
+ );
+
+ $build->set_column("job", "runCommandHook");
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build),
+ 0,
+ "The job name does not match"
+ );
+
+ $build->set_column("job", "runCommandHook.");
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build),
+ 0,
+ "The job name does not match"
+ );
+ };
+
+ subtest "On outputs ..." => sub {
+ ok(!warns {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.example"}),
+ 1,
+ "out is an executable file"
+ );
+ }, "No warnings for an executable file.");
+
+ ok(!warns {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.symlink"}),
+ 1,
+ "out is a symlink to an executable file"
+ );
+ }, "No warnings for a symlink to an executable file.");
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.no-out"}),
+ 0,
+ "No output named out"
+ );
+ }, qr/rejected: no output named 'out'/, "A relevant warning is provided for a missing output");
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.out-is-directory"}),
+ 0,
+ "out is a directory"
+ );
+ }, qr/output is not a regular file or symlink/, "A relevant warning is provided for a directory output");
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.out-is-not-executable-file"}),
+ 0,
+ "out is a file which is not a regular file or symlink"
+ );
+ }, qr/output is not executable/, "A relevant warning is provided if the file isn't executable");
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.symlink-non-executable"}),
+ 0,
+ "out is a symlink to a non-executable file"
+ );
+ }, qr/output is not executable/, "A relevant warning is provided for symlinks to non-executables");
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.symlink-directory"}),
+ 0,
+ "out is a symlink to a directory"
+ );
+ }, qr/output is not a regular file or symlink/, "A relevant warning is provided for symlinks to directories");
+ };
+
+ subtest "On build status ..." => sub {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.failed"}),
+ 0,
+ "Failed builds don't get run"
+ );
+ };
+
+ subtest "With dynamic runcommand disabled ..." => sub {
+ subtest "disabled on the project, enabled on the jobset" => sub {
+ $build->project->update({enable_dynamic_run_command => 0});
+ $build->jobset->update({enable_dynamic_run_command => 1});
+
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.example"}),
+ 0,
+ "Builds don't run from a jobset with disabled dynamic runcommand"
+ );
+ }, qr/project or jobset don't have dynamic runcommand enabled./, "A relevant warning is provided for a disabled runcommand support")
+ };
+
+ subtest "enabled on the project, disabled on the jobset" => sub {
+ $build->project->update({enable_dynamic_run_command => 1});
+ $build->jobset->update({enable_dynamic_run_command => 0});
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.example"}),
+ 0,
+ "Builds don't run from a jobset with disabled dynamic runcommand"
+ );
+ }, qr/project or jobset don't have dynamic runcommand enabled./, "A relevant warning is provided for a disabled runcommand support")
+ };
+
+ subtest "disabled on the project, disabled on the jobset" => sub {
+ $build->project->update({enable_dynamic_run_command => 0});
+ $build->jobset->update({enable_dynamic_run_command => 0});
+
+ like(warning {
+ is(
+ Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.example"}),
+ 0,
+ "Builds don't run from a jobset with disabled dynamic runcommand"
+ );
+ }, qr/project or jobset don't have dynamic runcommand enabled./, "A relevant warning is provided for a disabled runcommand support")
+ };
+ };
+};
+
+
+done_testing;
diff --git a/t/Hydra/Plugin/RunCommand/matcher.t b/t/Hydra/Plugin/RunCommand/matcher.t
index bc40ba77..ca74a84c 100644
--- a/t/Hydra/Plugin/RunCommand/matcher.t
+++ b/t/Hydra/Plugin/RunCommand/matcher.t
@@ -7,13 +7,13 @@ use Hydra::Plugin::RunCommand;
subtest "isEnabled" => sub {
is(
Hydra::Plugin::RunCommand::isEnabled({}),
- "",
+ 0,
"Disabled by default."
);
is(
Hydra::Plugin::RunCommand::isEnabled({ config => {}}),
- "",
+ 0,
"Disabled by default."
);
@@ -22,6 +22,121 @@ subtest "isEnabled" => sub {
1,
"Enabled if any runcommand blocks exist."
);
+
+ is(
+ Hydra::Plugin::RunCommand::isEnabled({ config => { dynamicruncommand => {}}}),
+ 0,
+ "Not enabled if an empty dynamicruncommand blocks exist."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::isEnabled({ config => { dynamicruncommand => { enable => 0 }}}),
+ 0,
+ "Not enabled if a dynamicruncommand blocks exist without enable being set to 1."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::isEnabled({ config => { dynamicruncommand => { enable => 1 }}}),
+ 1,
+ "Enabled if a dynamicruncommand blocks exist with enable being set to 1."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::isEnabled({ config => {
+ runcommand => {},
+ dynamicruncommand => { enable => 0 }
+ }}),
+ 1,
+ "Enabled if a runcommand config block exists, even if a dynamicruncommand is explicitly disabled."
+ );
+};
+
+subtest "areStaticCommandsEnabled" => sub {
+ is(
+ Hydra::Plugin::RunCommand::areStaticCommandsEnabled({}),
+ 0,
+ "Disabled by default."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areStaticCommandsEnabled({}),
+ 0,
+ "Disabled by default."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areStaticCommandsEnabled({ runcommand => {}}),
+ 1,
+ "Enabled if any runcommand blocks exist."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areStaticCommandsEnabled({ dynamicruncommand => {}}),
+ 0,
+ "Not enabled by dynamicruncommand blocks."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areStaticCommandsEnabled({ dynamicruncommand => { enable => 0 }}),
+ 0,
+ "Not enabled by dynamicruncommand blocks."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areStaticCommandsEnabled({ dynamicruncommand => { enable => 1 }}),
+ 0,
+ "Not enabled by dynamicruncommand blocks."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areStaticCommandsEnabled({
+ runcommand => {},
+ dynamicruncommand => { enable => 0 }
+ }),
+ 1,
+ "Enabled if a runcommand config block exists, even if a dynamicruncommand is explicitly disabled."
+ );
+};
+
+subtest "areDynamicCommandsEnabled" => sub {
+ is(
+ Hydra::Plugin::RunCommand::areDynamicCommandsEnabled({}),
+ 0,
+ "Disabled by default."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areDynamicCommandsEnabled({ runcommand => {}}),
+ 0,
+ "Disabled even if any runcommand blocks exist."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areDynamicCommandsEnabled({ dynamicruncommand => {}}),
+ 0,
+ "Not enabled if an empty dynamicruncommand blocks exist."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areDynamicCommandsEnabled({ dynamicruncommand => { enable => 0 }}),
+ 0,
+ "Not enabled if a dynamicruncommand blocks exist without enable being set to 1."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areDynamicCommandsEnabled({ dynamicruncommand => { enable => 1 }}),
+ 1,
+ "Enabled if a dynamicruncommand blocks exist with enable being set to 1."
+ );
+
+ is(
+ Hydra::Plugin::RunCommand::areDynamicCommandsEnabled({
+ runcommand => {},
+ dynamicruncommand => { enable => 0 }
+ }),
+ 0,
+ "Disabled if dynamicruncommand is explicitly disabled."
+ );
};
subtest "configSectionMatches" => sub {
@@ -134,44 +249,4 @@ subtest "eventMatches" => sub {
);
};
-subtest "fanoutToCommands" => sub {
- my $config = {
- runcommand => [
- {
- job => "",
- command => "foo"
- },
- {
- job => "project:*:*",
- command => "bar"
- },
- {
- job => "project:jobset:nomatch",
- command => "baz"
- }
- ]
- };
-
- is(
- Hydra::Plugin::RunCommand::fanoutToCommands(
- $config,
- "buildFinished",
- "project",
- "jobset",
- "job"
- ),
- [
- {
- matcher => "",
- command => "foo"
- },
- {
- matcher => "project:*:*",
- command => "bar"
- }
- ],
- "fanoutToCommands returns a command per matching job"
- );
-};
-
done_testing;
diff --git a/t/jobs/runcommand-dynamic.nix b/t/jobs/runcommand-dynamic.nix
new file mode 100644
index 00000000..1971bb82
--- /dev/null
+++ b/t/jobs/runcommand-dynamic.nix
@@ -0,0 +1,148 @@
+with import ./config.nix;
+rec {
+ foo-bar-baz = mkDerivation {
+ name = "foo-bar-baz";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ touch $out
+ ''
+ )
+ ];
+ };
+
+ runCommandHook.example = mkDerivation {
+ name = "my-build-product";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ touch $out
+ chmod +x $out
+ # ... dunno ...
+ ''
+ )
+ ];
+ };
+
+ runCommandHook.symlink = mkDerivation {
+ name = "symlink-out";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ ln -s $1 $out
+ ''
+ )
+
+ runCommandHook.example
+ ];
+ };
+
+ runCommandHook.no-out = mkDerivation {
+ name = "no-out";
+ builder = "/bin/sh";
+ outputs = [ "bin" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+ mkdir $bin
+ ''
+ )
+ ];
+ };
+
+ runCommandHook.out-is-directory = mkDerivation {
+ name = "out-is-directory";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ mkdir $out
+ ''
+ )
+ ];
+ };
+
+ runCommandHook.out-is-not-executable-file = mkDerivation {
+ name = "out-is-directory";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ touch $out
+ ''
+ )
+ ];
+ };
+
+ runCommandHook.symlink-non-executable = mkDerivation {
+ name = "symlink-out";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ ln -s $1 $out
+ ''
+ )
+
+ runCommandHook.out-is-not-executable-file
+ ];
+ };
+
+ runCommandHook.symlink-directory = mkDerivation {
+ name = "symlink-directory";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ ln -s $1 $out
+ ''
+ )
+
+ runCommandHook.out-is-directory
+ ];
+ };
+
+ runCommandHook.failed = mkDerivation {
+ name = "failed";
+ builder = "/bin/sh";
+ outputs = [ "out" ];
+ args = [
+ (
+ builtins.toFile "builder.sh" ''
+ #! /bin/sh
+
+ touch $out
+ chmod +x $out
+
+ exit 1
+ ''
+ )
+ ];
+ };
+
+}