forked from lix-project/hydra
Merge pull request #1092 from DeterminateSystems/restart-failed-no-eval
Allow restarting failed builds even with no eval to compare to
This commit is contained in:
commit
ff26ce0d06
|
@ -6,6 +6,7 @@ use warnings;
|
||||||
use base 'Hydra::Base::Controller::NixChannel';
|
use base 'Hydra::Base::Controller::NixChannel';
|
||||||
use Hydra::Helper::Nix;
|
use Hydra::Helper::Nix;
|
||||||
use Hydra::Helper::CatalystUtils;
|
use Hydra::Helper::CatalystUtils;
|
||||||
|
use Hydra::Helper::BuildDiff;
|
||||||
use List::SomeUtils qw(uniq);
|
use List::SomeUtils qw(uniq);
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,63 +64,19 @@ sub view_GET {
|
||||||
|
|
||||||
$c->stash->{otherEval} = $eval2 if defined $eval2;
|
$c->stash->{otherEval} = $eval2 if defined $eval2;
|
||||||
|
|
||||||
sub cmpBuilds {
|
|
||||||
my ($left, $right) = @_;
|
|
||||||
return $left->get_column('job') cmp $right->get_column('job')
|
|
||||||
|| $left->get_column('system') cmp $right->get_column('system')
|
|
||||||
}
|
|
||||||
|
|
||||||
my @builds = $eval->builds->search($filter, { columns => [@buildListColumns] });
|
my @builds = $eval->builds->search($filter, { columns => [@buildListColumns] });
|
||||||
my @builds2 = defined $eval2 ? $eval2->builds->search($filter, { columns => [@buildListColumns] }) : ();
|
my @builds2 = defined $eval2 ? $eval2->builds->search($filter, { columns => [@buildListColumns] }) : ();
|
||||||
|
|
||||||
@builds = sort { cmpBuilds($a, $b) } @builds;
|
my $diff = buildDiff([@builds], [@builds2]);
|
||||||
@builds2 = sort { cmpBuilds($a, $b) } @builds2;
|
$c->stash->{stillSucceed} = $diff->{stillSucceed};
|
||||||
|
$c->stash->{stillFail} = $diff->{stillFail};
|
||||||
$c->stash->{stillSucceed} = [];
|
$c->stash->{nowSucceed} = $diff->{nowSucceed};
|
||||||
$c->stash->{stillFail} = [];
|
$c->stash->{nowFail} = $diff->{nowFail};
|
||||||
$c->stash->{nowSucceed} = [];
|
$c->stash->{new} = $diff->{new};
|
||||||
$c->stash->{nowFail} = [];
|
$c->stash->{removed} = $diff->{removed};
|
||||||
$c->stash->{new} = [];
|
$c->stash->{unfinished} = $diff->{unfinished};
|
||||||
$c->stash->{removed} = [];
|
$c->stash->{aborted} = $diff->{aborted};
|
||||||
$c->stash->{unfinished} = [];
|
$c->stash->{failed} = $diff->{failed};
|
||||||
$c->stash->{aborted} = [];
|
|
||||||
|
|
||||||
my $n = 0;
|
|
||||||
foreach my $build (@builds) {
|
|
||||||
my $aborted = $build->finished != 0 && ($build->buildstatus == 3 || $build->buildstatus == 4);
|
|
||||||
my $d;
|
|
||||||
my $found = 0;
|
|
||||||
while ($n < scalar(@builds2)) {
|
|
||||||
my $build2 = $builds2[$n];
|
|
||||||
my $d = cmpBuilds($build, $build2);
|
|
||||||
last if $d == -1;
|
|
||||||
if ($d == 0) {
|
|
||||||
$n++;
|
|
||||||
$found = 1;
|
|
||||||
if ($aborted) {
|
|
||||||
# do nothing
|
|
||||||
} elsif ($build->finished == 0 || $build2->finished == 0) {
|
|
||||||
push @{$c->stash->{unfinished}}, $build;
|
|
||||||
} elsif ($build->buildstatus == 0 && $build2->buildstatus == 0) {
|
|
||||||
push @{$c->stash->{stillSucceed}}, $build;
|
|
||||||
} elsif ($build->buildstatus != 0 && $build2->buildstatus != 0) {
|
|
||||||
push @{$c->stash->{stillFail}}, $build;
|
|
||||||
} elsif ($build->buildstatus == 0 && $build2->buildstatus != 0) {
|
|
||||||
push @{$c->stash->{nowSucceed}}, $build;
|
|
||||||
} elsif ($build->buildstatus != 0 && $build2->buildstatus == 0) {
|
|
||||||
push @{$c->stash->{nowFail}}, $build;
|
|
||||||
} else { die; }
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
push @{$c->stash->{removed}}, { job => $build2->get_column('job'), system => $build2->get_column('system') };
|
|
||||||
$n++;
|
|
||||||
}
|
|
||||||
if ($aborted) {
|
|
||||||
push @{$c->stash->{aborted}}, $build;
|
|
||||||
} else {
|
|
||||||
push @{$c->stash->{new}}, $build if !$found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$c->stash->{full} = ($c->req->params->{full} || "0") eq "1";
|
$c->stash->{full} = ($c->req->params->{full} || "0") eq "1";
|
||||||
|
|
||||||
|
|
82
src/lib/Hydra/Helper/BuildDiff.pm
Normal file
82
src/lib/Hydra/Helper/BuildDiff.pm
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package Hydra::Helper::BuildDiff;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
our @ISA = qw(Exporter);
|
||||||
|
our @EXPORT = qw(
|
||||||
|
buildDiff
|
||||||
|
);
|
||||||
|
|
||||||
|
sub cmpBuilds {
|
||||||
|
my ($left, $right) = @_;
|
||||||
|
return $left->get_column('job') cmp $right->get_column('job')
|
||||||
|
|| $left->get_column('system') cmp $right->get_column('system')
|
||||||
|
}
|
||||||
|
|
||||||
|
sub buildDiff {
|
||||||
|
# $builds is the list of current builds
|
||||||
|
# $builds2 is the list of previous (to-be-compared-to) builds
|
||||||
|
my ($builds, $builds2) = @_;
|
||||||
|
|
||||||
|
$builds = [sort { cmpBuilds($a, $b) } @{$builds}];
|
||||||
|
$builds2 = [sort { cmpBuilds($a, $b) } @{$builds2}];
|
||||||
|
|
||||||
|
my $ret = {
|
||||||
|
stillSucceed => [],
|
||||||
|
stillFail => [],
|
||||||
|
nowSucceed => [],
|
||||||
|
nowFail => [],
|
||||||
|
new => [],
|
||||||
|
removed => [],
|
||||||
|
unfinished => [],
|
||||||
|
aborted => [],
|
||||||
|
failed => [],
|
||||||
|
};
|
||||||
|
|
||||||
|
my $n = 0;
|
||||||
|
foreach my $build (@{$builds}) {
|
||||||
|
my $aborted = $build->finished != 0 && ($build->buildstatus == 3 || $build->buildstatus == 4);
|
||||||
|
my $d;
|
||||||
|
my $found = 0;
|
||||||
|
while ($n < scalar(@{$builds2})) {
|
||||||
|
my $build2 = @{$builds2}[$n];
|
||||||
|
my $d = cmpBuilds($build, $build2);
|
||||||
|
last if $d == -1;
|
||||||
|
if ($d == 0) {
|
||||||
|
$n++;
|
||||||
|
$found = 1;
|
||||||
|
if ($aborted) {
|
||||||
|
# do nothing
|
||||||
|
} elsif ($build->finished == 0 || $build2->finished == 0) {
|
||||||
|
push @{$ret->{unfinished}}, $build;
|
||||||
|
} elsif ($build->buildstatus == 0 && $build2->buildstatus == 0) {
|
||||||
|
push @{$ret->{stillSucceed}}, $build;
|
||||||
|
} elsif ($build->buildstatus != 0 && $build2->buildstatus != 0) {
|
||||||
|
push @{$ret->{stillFail}}, $build;
|
||||||
|
} elsif ($build->buildstatus == 0 && $build2->buildstatus != 0) {
|
||||||
|
push @{$ret->{nowSucceed}}, $build;
|
||||||
|
} elsif ($build->buildstatus != 0 && $build2->buildstatus == 0) {
|
||||||
|
push @{$ret->{nowFail}}, $build;
|
||||||
|
} else { die; }
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
my $job_system = { job => $build2->get_column('job'), system => $build2->get_column('system') };
|
||||||
|
push @{$ret->{removed}}, $job_system;
|
||||||
|
$n++;
|
||||||
|
}
|
||||||
|
if ($aborted) {
|
||||||
|
push @{$ret->{aborted}}, $build;
|
||||||
|
} else {
|
||||||
|
push @{$ret->{new}}, $build if !$found;
|
||||||
|
}
|
||||||
|
if (defined $build->buildstatus && $build->buildstatus != 0) {
|
||||||
|
push @{$ret->{failed}}, $build;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
|
@ -51,7 +51,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'),
|
||||||
[% IF unfinished.size > 0 %]
|
[% IF unfinished.size > 0 %]
|
||||||
<a class="dropdown-item" href="[% c.uri_for(c.controller('JobsetEval').action_for('cancel'), [eval.id]) %]">Cancel all scheduled builds</a>
|
<a class="dropdown-item" href="[% c.uri_for(c.controller('JobsetEval').action_for('cancel'), [eval.id]) %]">Cancel all scheduled builds</a>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% IF aborted.size > 0 || stillFail.size > 0 || nowFail.size > 0 %]
|
[% IF aborted.size > 0 || stillFail.size > 0 || nowFail.size > 0 || failed.size > 0 %]
|
||||||
<a class="dropdown-item" href="[% c.uri_for(c.controller('JobsetEval').action_for('restart_failed'), [eval.id]) %]">Restart all failed builds</a>
|
<a class="dropdown-item" href="[% c.uri_for(c.controller('JobsetEval').action_for('restart_failed'), [eval.id]) %]">Restart all failed builds</a>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% IF aborted.size > 0 %]
|
[% IF aborted.size > 0 %]
|
||||||
|
|
28
t/Controller/JobsetEval/fetch.t
Normal file
28
t/Controller/JobsetEval/fetch.t
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use feature 'unicode_strings';
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Setup;
|
||||||
|
use JSON::MaybeXS qw(decode_json encode_json);
|
||||||
|
|
||||||
|
my %ctx = test_init();
|
||||||
|
|
||||||
|
require Hydra::Schema;
|
||||||
|
require Hydra::Model::DB;
|
||||||
|
require Hydra::Helper::Nix;
|
||||||
|
|
||||||
|
use Test2::V0;
|
||||||
|
require Catalyst::Test;
|
||||||
|
Catalyst::Test->import('Hydra');
|
||||||
|
use HTTP::Request::Common qw(POST PUT GET DELETE);
|
||||||
|
|
||||||
|
my $db = Hydra::Model::DB->new;
|
||||||
|
hydra_setup($db);
|
||||||
|
|
||||||
|
my $jobset = createBaseJobset("basic", "basic.nix", $ctx{jobsdir});
|
||||||
|
ok(evalSucceeds($jobset), "Evaluating jobs/basic.nix should exit with return code 0");
|
||||||
|
|
||||||
|
my ($eval, @evals) = $jobset->jobsetevals;
|
||||||
|
my $fetch = request(GET '/eval/' . $eval->id);
|
||||||
|
is($fetch->code, 200, "eval page is 200");
|
||||||
|
|
||||||
|
done_testing;
|
106
t/Helper/BuildDiff.t
Normal file
106
t/Helper/BuildDiff.t
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Setup;
|
||||||
|
use Test2::V0;
|
||||||
|
|
||||||
|
use Hydra::Helper::BuildDiff;
|
||||||
|
|
||||||
|
my $ctx = test_context();
|
||||||
|
|
||||||
|
my $builds = $ctx->makeAndEvaluateJobset(
|
||||||
|
expression => "basic.nix",
|
||||||
|
build => 1
|
||||||
|
);
|
||||||
|
|
||||||
|
subtest "empty diff" => sub {
|
||||||
|
my $ret = buildDiff([], []);
|
||||||
|
is(
|
||||||
|
$ret,
|
||||||
|
{
|
||||||
|
stillSucceed => [],
|
||||||
|
stillFail => [],
|
||||||
|
nowSucceed => [],
|
||||||
|
nowFail => [],
|
||||||
|
new => [],
|
||||||
|
removed => [],
|
||||||
|
unfinished => [],
|
||||||
|
aborted => [],
|
||||||
|
failed => [],
|
||||||
|
},
|
||||||
|
"empty list of jobs returns empty diff"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
subtest "2 different jobs" => sub {
|
||||||
|
my $ret = buildDiff([$builds->{"succeed_with_failed"}], [$builds->{"empty_dir"}]);
|
||||||
|
|
||||||
|
is($ret->{stillSucceed}, [], "stillSucceed");
|
||||||
|
is($ret->{stillFail}, [], "stillFail");
|
||||||
|
is($ret->{nowSucceed}, [], "nowSucceed");
|
||||||
|
is($ret->{nowFail}, [], "nowFail");
|
||||||
|
is($ret->{unfinished}, [], "unfinished");
|
||||||
|
is($ret->{aborted}, [], "aborted");
|
||||||
|
|
||||||
|
is(scalar(@{$ret->{new}}), 1, "list of new jobs is 1 element long");
|
||||||
|
is(
|
||||||
|
$ret->{new}[0]->get_column('id'),
|
||||||
|
$builds->{"succeed_with_failed"}->get_column('id'),
|
||||||
|
"succeed_with_failed is a new job"
|
||||||
|
);
|
||||||
|
|
||||||
|
is(scalar(@{$ret->{failed}}), 1, "list of failed jobs is 1 element long");
|
||||||
|
is(
|
||||||
|
$ret->{failed}[0]->get_column('id'),
|
||||||
|
$builds->{"succeed_with_failed"}->get_column('id'),
|
||||||
|
"succeed_with_failed is a failed job"
|
||||||
|
);
|
||||||
|
|
||||||
|
is(
|
||||||
|
$ret->{removed},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
job => $builds->{"empty_dir"}->get_column('job'),
|
||||||
|
system => $builds->{"empty_dir"}->get_column('system')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"empty_dir is a removed job"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
subtest "failed job with no previous history" => sub {
|
||||||
|
my $ret = buildDiff([$builds->{"fails"}], []);
|
||||||
|
|
||||||
|
is(scalar(@{$ret->{failed}}), 1, "list of failed jobs is 1 element long");
|
||||||
|
is(
|
||||||
|
$ret->{failed}[0]->get_column('id'),
|
||||||
|
$builds->{"fails"}->get_column('id'),
|
||||||
|
"fails is a failed job"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
subtest "not-yet-built job with no previous history" => sub {
|
||||||
|
my $builds = $ctx->makeAndEvaluateJobset(
|
||||||
|
expression => "build-products.nix",
|
||||||
|
build => 0
|
||||||
|
);
|
||||||
|
|
||||||
|
my $ret = buildDiff([$builds->{"simple"}], []);
|
||||||
|
|
||||||
|
is($ret->{stillSucceed}, [], "stillSucceed");
|
||||||
|
is($ret->{stillFail}, [], "stillFail");
|
||||||
|
is($ret->{nowSucceed}, [], "nowSucceed");
|
||||||
|
is($ret->{nowFail}, [], "nowFail");
|
||||||
|
is($ret->{removed}, [], "removed");
|
||||||
|
is($ret->{unfinished}, [], "unfinished");
|
||||||
|
is($ret->{aborted}, [], "aborted");
|
||||||
|
is($ret->{failed}, [], "failed");
|
||||||
|
|
||||||
|
is(scalar(@{$ret->{new}}), 1, "list of new jobs is 1 element long");
|
||||||
|
is(
|
||||||
|
$ret->{new}[0]->get_column('id'),
|
||||||
|
$builds->{"simple"}->get_column('id'),
|
||||||
|
"simple is a new job"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
done_testing;
|
Loading…
Reference in a new issue