diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm index 6f6e7eab..30179d49 100644 --- a/src/lib/Hydra/Controller/JobsetEval.pm +++ b/src/lib/Hydra/Controller/JobsetEval.pm @@ -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"; diff --git a/src/lib/Hydra/Helper/BuildDiff.pm b/src/lib/Hydra/Helper/BuildDiff.pm new file mode 100644 index 00000000..cd8c7691 --- /dev/null +++ b/src/lib/Hydra/Helper/BuildDiff.pm @@ -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; \ No newline at end of file diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 25315c66..8a8d92e4 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -51,7 +51,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% IF unfinished.size > 0 %] Cancel all scheduled builds [% END %] - [% IF aborted.size > 0 || stillFail.size > 0 || nowFail.size > 0 %] + [% IF aborted.size > 0 || stillFail.size > 0 || nowFail.size > 0 || failed.size > 0 %] Restart all failed builds [% END %] [% IF aborted.size > 0 %] diff --git a/t/Controller/JobsetEval/fetch.t b/t/Controller/JobsetEval/fetch.t new file mode 100644 index 00000000..b129de75 --- /dev/null +++ b/t/Controller/JobsetEval/fetch.t @@ -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; diff --git a/t/Helper/BuildDiff.t b/t/Helper/BuildDiff.t new file mode 100644 index 00000000..243bb596 --- /dev/null +++ b/t/Helper/BuildDiff.t @@ -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;