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 Hydra::Helper::Nix;
|
||||
use Hydra::Helper::CatalystUtils;
|
||||
use Hydra::Helper::BuildDiff;
|
||||
use List::SomeUtils qw(uniq);
|
||||
|
||||
|
||||
|
@ -63,63 +64,19 @@ sub view_GET {
|
|||
|
||||
$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 @builds2 = defined $eval2 ? $eval2->builds->search($filter, { columns => [@buildListColumns] }) : ();
|
||||
|
||||
@builds = sort { cmpBuilds($a, $b) } @builds;
|
||||
@builds2 = sort { cmpBuilds($a, $b) } @builds2;
|
||||
|
||||
$c->stash->{stillSucceed} = [];
|
||||
$c->stash->{stillFail} = [];
|
||||
$c->stash->{nowSucceed} = [];
|
||||
$c->stash->{nowFail} = [];
|
||||
$c->stash->{new} = [];
|
||||
$c->stash->{removed} = [];
|
||||
$c->stash->{unfinished} = [];
|
||||
$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;
|
||||
}
|
||||
}
|
||||
my $diff = buildDiff([@builds], [@builds2]);
|
||||
$c->stash->{stillSucceed} = $diff->{stillSucceed};
|
||||
$c->stash->{stillFail} = $diff->{stillFail};
|
||||
$c->stash->{nowSucceed} = $diff->{nowSucceed};
|
||||
$c->stash->{nowFail} = $diff->{nowFail};
|
||||
$c->stash->{new} = $diff->{new};
|
||||
$c->stash->{removed} = $diff->{removed};
|
||||
$c->stash->{unfinished} = $diff->{unfinished};
|
||||
$c->stash->{aborted} = $diff->{aborted};
|
||||
$c->stash->{failed} = $diff->{failed};
|
||||
|
||||
$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 %]
|
||||
<a class="dropdown-item" href="[% c.uri_for(c.controller('JobsetEval').action_for('cancel'), [eval.id]) %]">Cancel all scheduled builds</a>
|
||||
[% 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>
|
||||
[% END %]
|
||||
[% 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