Merge pull request #1103 from DeterminateSystems/runcommand/dynamic

Dynamic RunCommand
This commit is contained in:
Graham Christensen
2022-04-19 10:09:47 -04:00
committed by GitHub
29 changed files with 1233 additions and 66 deletions

View File

@ -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 => "",

View File

@ -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 => [],

View File

@ -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;

View File

@ -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|
<dynamicruncommand>
enable = 1
</dynamicruncommand>
|
);
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;

View File

@ -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;

View File

@ -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;