From eca09bc980b701717115b35e28cc9171d7dccca5 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Wed, 8 Dec 2021 13:31:34 -0500 Subject: [PATCH 01/10] Attempt to fix errors during test teardown --- t/View/TT.t | 20 ++---- t/evaluate-basic.t | 12 +--- t/lib/HydraTestContext.pm | 130 ++++++++++++++++++++++++++++++++++++++ t/lib/Setup.pm | 74 +++++----------------- 4 files changed, 156 insertions(+), 80 deletions(-) create mode 100644 t/lib/HydraTestContext.pm diff --git a/t/View/TT.t b/t/View/TT.t index 378bbbdb..a2401976 100644 --- a/t/View/TT.t +++ b/t/View/TT.t @@ -2,31 +2,25 @@ use feature 'unicode_strings'; use strict; use warnings; use Setup; - -my %ctx = test_init(); - -require Hydra::Schema; -require Hydra::Model::DB; - use Test2::V0; +my $ctx = test_context(); + require Hydra; # calls setup() - - -my $db = Hydra::Model::DB->new; -hydra_setup($db); - require Hydra::View::TT; +require Catalyst::Test; + +my $db = $ctx->db; + # The following lines are a cheap and hacky trick to get $c, # there is no other reason to call /. -require Catalyst::Test; Catalyst::Test->import('Hydra'); my ($_request, $c) = ctx_request('/'); my $project = $db->resultset('Projects')->create({name => "tests", displayname => "", owner => "root"}); -my $jobset = createBaseJobset("example", "bogus.nix", $ctx{jobsdir}); +my $jobset = createBaseJobset("example", "bogus.nix", $ctx->jobsdir); my $job = "myjob"; diff --git a/t/evaluate-basic.t b/t/evaluate-basic.t index f4c6fde8..d3f21564 100644 --- a/t/evaluate-basic.t +++ b/t/evaluate-basic.t @@ -2,21 +2,15 @@ use feature 'unicode_strings'; use strict; use warnings; use Setup; - -my %ctx = test_init(); - -require Hydra::Schema; -require Hydra::Model::DB; - use Test2::V0; -my $db = Hydra::Model::DB->new; -hydra_setup($db); +my $ctx = test_context(); +my $db = $ctx->db; my $project = $db->resultset('Projects')->create({name => "tests", displayname => "", owner => "root"}); # Most basic test case, no parameters -my $jobset = createBaseJobset("basic", "basic.nix", $ctx{jobsdir}); +my $jobset = createBaseJobset("basic", "basic.nix", $ctx->jobsdir); ok(evalSucceeds($jobset), "Evaluating jobs/basic.nix should exit with return code 0"); is(nrQueuedBuildsForJobset($jobset), 3, "Evaluating jobs/basic.nix should result in 3 builds"); diff --git a/t/lib/HydraTestContext.pm b/t/lib/HydraTestContext.pm new file mode 100644 index 00000000..3395f0a7 --- /dev/null +++ b/t/lib/HydraTestContext.pm @@ -0,0 +1,130 @@ +use strict; +use warnings; + +package HydraTestContext; +use File::Path qw(make_path); +use File::Basename; +use Cwd qw(abs_path getcwd); + +# Set up the environment for running tests. +# +# Hash Parameters: +# +# * hydra_config: configuration for the Hydra processes for your test. +# * nix_config: text to include in the test's nix.conf +# * use_external_destination_store: Boolean indicating whether hydra should +# use a destination store different from the evaluation store. +# True by default. +# +# This clears several environment variables and sets them to ephemeral +# values: a temporary database, temporary Nix store, temporary Hydra +# data directory, etc. +# +# Note: This function must run _very_ early, before nearly any Hydra +# libraries are loaded. To use this, you very likely need to `use Setup` +# and then run `test_init`, and then `require` the Hydra libraries you +# need. +# +# It returns a tuple: a handle to a temporary directory and a handle to +# the postgres service. If either of these variables go out of scope, +# those resources are released and the test environment becomes invalid. +# +# Look at the top of an existing `.t` file to see how this should be used +# in practice. +sub new { + my ($class, %opts) = @_; + + my $dir = File::Temp->newdir(); + + $ENV{'HYDRA_DATA'} = "$dir/hydra-data"; + mkdir $ENV{'HYDRA_DATA'}; + $ENV{'NIX_CONF_DIR'} = "$dir/nix/etc/nix"; + make_path($ENV{'NIX_CONF_DIR'}); + my $nixconf = "$ENV{'NIX_CONF_DIR'}/nix.conf"; + my $nix_config = "sandbox = false\n" . ($opts{'nix_config'} || ""); + write_file($nixconf, $nix_config); + $ENV{'HYDRA_CONFIG'} = "$dir/hydra.conf"; + + my $hydra_config = $opts{'hydra_config'} || ""; + if ($opts{'use_external_destination_store'} // 1) { + $hydra_config = "store_uri = file:$dir/nix/dest-store\n" . $hydra_config; + } + + write_file($ENV{'HYDRA_CONFIG'}, $hydra_config); + + $ENV{'NIX_LOG_DIR'} = "$dir/nix/var/log/nix"; + $ENV{'NIX_REMOTE_SYSTEMS'} = ''; + $ENV{'NIX_REMOTE'} = ''; + $ENV{'NIX_STATE_DIR'} = "$dir/nix/var/nix"; + $ENV{'NIX_STORE_DIR'} = "$dir/nix/store"; + + my $pgsql = Test::PostgreSQL->new( + extra_initdb_args => "--locale C.UTF-8" + ); + $ENV{'HYDRA_DBI'} = $pgsql->dsn; + system("hydra-init") == 0 or die; + + my $self = { + _db => undef, + db_handle => $pgsql, + tmpdir => $dir, + testdir => abs_path(dirname(__FILE__) . "/.."), + jobsdir => abs_path(dirname(__FILE__) . "/../jobs") + }; + + return bless $self, $class; +} + +sub db { + my ($self, $setup) = @_; + + + if (!defined $self->{_db}) { + require Hydra::Schema; + require Hydra::Model::DB; + $self->{_db} = Hydra::Model::DB->new(); + + if (!(defined $setup && $setup == 0)) { + $self->{_db}->resultset('Users')->create({ + username => "root", + emailaddress => 'root@invalid.org', + password => '' + }); + } + } + + return $self->{_db}; +} + +sub tmpdir { + my ($self) = @_; + + return $self->{tmpdir}; +} + +sub testdir { + my ($self) = @_; + + return $self->{testdir}; +} + +sub jobsdir { + my ($self) = @_; + + return $self->{jobsdir}; +} + +sub DESTROY +{ + my ($self) = @_; + $self->db(0)->schema->storage->disconnect(); +} + +sub write_file { + my ($path, $text) = @_; + open(my $fh, '>', $path) or die "Could not open file '$path' $!"; + print $fh $text || ""; + close $fh; +} + +1; diff --git a/t/lib/Setup.pm b/t/lib/Setup.pm index 983a8c56..3887784c 100644 --- a/t/lib/Setup.pm +++ b/t/lib/Setup.pm @@ -10,74 +10,32 @@ use File::Basename; use Cwd qw(abs_path getcwd); our @ISA = qw(Exporter); -our @EXPORT = qw(test_init hydra_setup write_file nrBuildsForJobset queuedBuildsForJobset +our @EXPORT = qw(test_context test_init hydra_setup write_file nrBuildsForJobset queuedBuildsForJobset nrQueuedBuildsForJobset createBaseJobset createJobsetWithOneInput evalSucceeds runBuild sendNotifications updateRepository captureStdoutStderr); # Set up the environment for running tests. # -# Hash Parameters: +# See HydraTestContext::new for documentation +sub test_context { + require HydraTestContext; + return HydraTestContext->new(@_); +} + +# Set up the environment for running tests. # -# * hydra_config: configuration for the Hydra processes for your test. -# * nix_config: text to include in the test's nix.conf -# * use_external_destination_store: Boolean indicating whether hydra should -# use a destination store different from the evaluation store. -# True by default. -# -# This clears several environment variables and sets them to ephemeral -# values: a temporary database, temporary Nix store, temporary Hydra -# data directory, etc. -# -# Note: This function must run _very_ early, before nearly any Hydra -# libraries are loaded. To use this, you very likely need to `use Setup` -# and then run `test_init`, and then `require` the Hydra libraries you -# need. -# -# It returns a tuple: a handle to a temporary directory and a handle to -# the postgres service. If either of these variables go out of scope, -# those resources are released and the test environment becomes invalid. -# -# Look at the top of an existing `.t` file to see how this should be used -# in practice. +# See HydraTestContext::new for documentation sub test_init { - my %opts = @_; + require HydraTestContext; + my $ctx = HydraTestContext->new(@_); - my $dir = File::Temp->newdir(); - - $ENV{'HYDRA_DATA'} = "$dir/hydra-data"; - mkdir $ENV{'HYDRA_DATA'}; - $ENV{'NIX_CONF_DIR'} = "$dir/nix/etc/nix"; - make_path($ENV{'NIX_CONF_DIR'}); - my $nixconf = "$ENV{'NIX_CONF_DIR'}/nix.conf"; - my $nix_config = "sandbox = false\n" . ($opts{'nix_config'} || ""); - write_file($nixconf, $nix_config); - $ENV{'HYDRA_CONFIG'} = "$dir/hydra.conf"; - - my $hydra_config = $opts{'hydra_config'} || ""; - if ($opts{'use_external_destination_store'} // 1) { - $hydra_config = "store_uri = file:$dir/nix/dest-store\n" . $hydra_config; - } - - write_file($ENV{'HYDRA_CONFIG'}, $hydra_config); - - $ENV{'NIX_LOG_DIR'} = "$dir/nix/var/log/nix"; - $ENV{'NIX_REMOTE_SYSTEMS'} = ''; - $ENV{'NIX_REMOTE'} = ''; - $ENV{'NIX_STATE_DIR'} = "$dir/nix/var/nix"; - $ENV{'NIX_STORE_DIR'} = "$dir/nix/store"; - - my $pgsql = Test::PostgreSQL->new( - extra_initdb_args => "--locale C.UTF-8" - ); - $ENV{'HYDRA_DBI'} = $pgsql->dsn; - system("hydra-init") == 0 or die; return ( - db => $pgsql, - tmpdir => $dir, - testdir => abs_path(dirname(__FILE__) . "/.."), - jobsdir => abs_path(dirname(__FILE__) . "/../jobs") - ); + context => $ctx, + tmpdir => $ctx->tmpdir, + testdir => $ctx->testdir, + jobsdir => $ctx->jobsdir + ) } sub write_file { From 1fa141229f62ef188b9e1ec8689e7d80ab5feaf3 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 19:51:06 -0500 Subject: [PATCH 02/10] HydraTestContext: explicitly stop the db This might, hopefully, I don't know, possibly force the database to live a little while longer and *reduce* but not eliminate errors around stopping the database before we lose all our DB::PG handles to it. --- t/lib/HydraTestContext.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/t/lib/HydraTestContext.pm b/t/lib/HydraTestContext.pm index 3395f0a7..6d15aaa1 100644 --- a/t/lib/HydraTestContext.pm +++ b/t/lib/HydraTestContext.pm @@ -118,6 +118,7 @@ sub DESTROY { my ($self) = @_; $self->db(0)->schema->storage->disconnect(); + $self->{db_handle}->stop(); } sub write_file { From 3238496b3d771c1310bd456ef06303556186b181 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:25:02 -0500 Subject: [PATCH 03/10] t/Setup: move subs calling hydra-* programs to a CliPrograms module Makes it so HydraTestContext can call them without an import loop --- t/lib/CliRunners.pm | 50 +++++++++++++++++++++++++++++++++++++++++++++ t/lib/Setup.pm | 40 +----------------------------------- 2 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 t/lib/CliRunners.pm diff --git a/t/lib/CliRunners.pm b/t/lib/CliRunners.pm new file mode 100644 index 00000000..f857d468 --- /dev/null +++ b/t/lib/CliRunners.pm @@ -0,0 +1,50 @@ +use warnings; +use strict; + +package CliRunners; +our @ISA = qw(Exporter); +our @EXPORT = qw( + evalSucceeds runBuild sendNotifications + captureStdoutStderr); + + +sub captureStdoutStderr { + # "Lazy"-load Hydra::Helper::Nix to avoid the compile-time + # import of Hydra::Model::DB. Early loading of the DB class + # causes fixation of the DSN, and we need to fixate it after + # the temporary DB is setup. + require Hydra::Helper::Nix; + return Hydra::Helper::Nix::captureStdoutStderr(@_) +} + +sub evalSucceeds { + my ($jobset) = @_; + my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-eval-jobset", $jobset->project->name, $jobset->name)); + $jobset->discard_changes; # refresh from DB + chomp $stdout; chomp $stderr; + print STDERR "Evaluation errors for jobset ".$jobset->project->name.":".$jobset->name.": \n".$jobset->errormsg."\n" if $jobset->errormsg; + print STDERR "STDOUT: $stdout\n" if $stdout ne ""; + print STDERR "STDERR: $stderr\n" if $stderr ne ""; + return !$res; +} + +sub runBuild { + my ($build) = @_; + my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-queue-runner", "-vvvv", "--build-one", $build->id)); + if ($res) { + print STDERR "Queue runner stdout: $stdout\n" if $stdout ne ""; + print STDERR "Queue runner stderr: $stderr\n" if $stderr ne ""; + } + return !$res; +} + +sub sendNotifications() { + my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-notify", "--queued-only")); + if ($res) { + print STDERR "hydra notify stdout: $stdout\n" if $stdout ne ""; + print STDERR "hydra notify stderr: $stderr\n" if $stderr ne ""; + } + return !$res; +} + +1; diff --git a/t/lib/Setup.pm b/t/lib/Setup.pm index 3887784c..d0f0230b 100644 --- a/t/lib/Setup.pm +++ b/t/lib/Setup.pm @@ -8,6 +8,7 @@ use File::Temp; use File::Path qw(make_path); use File::Basename; use Cwd qw(abs_path getcwd); +use CliRunners; our @ISA = qw(Exporter); our @EXPORT = qw(test_context test_init hydra_setup write_file nrBuildsForJobset queuedBuildsForJobset @@ -45,15 +46,6 @@ sub write_file { close $fh; } -sub captureStdoutStderr { - # "Lazy"-load Hydra::Helper::Nix to avoid the compile-time - # import of Hydra::Model::DB. Early loading of the DB class - # causes fixation of the DSN, and we need to fixate it after - # the temporary DB is setup. - require Hydra::Helper::Nix; - return Hydra::Helper::Nix::captureStdoutStderr(@_) -} - sub hydra_setup { my ($db) = @_; $db->resultset('Users')->create({ username => "root", emailaddress => 'root@invalid.org', password => '' }); @@ -103,36 +95,6 @@ sub createJobsetWithOneInput { return $jobset; } -sub evalSucceeds { - my ($jobset) = @_; - my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-eval-jobset", $jobset->project->name, $jobset->name)); - $jobset->discard_changes; # refresh from DB - chomp $stdout; chomp $stderr; - print STDERR "Evaluation errors for jobset ".$jobset->project->name.":".$jobset->name.": \n".$jobset->errormsg."\n" if $jobset->errormsg; - print STDERR "STDOUT: $stdout\n" if $stdout ne ""; - print STDERR "STDERR: $stderr\n" if $stderr ne ""; - return !$res; -} - -sub runBuild { - my ($build) = @_; - my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-queue-runner", "-vvvv", "--build-one", $build->id)); - if ($res) { - print STDERR "Queue runner stdout: $stdout\n" if $stdout ne ""; - print STDERR "Queue runner stderr: $stderr\n" if $stderr ne ""; - } - return !$res; -} - -sub sendNotifications() { - my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-notify", "--queued-only")); - if ($res) { - print STDERR "hydra notify stdout: $stdout\n" if $stdout ne ""; - print STDERR "hydra notify stderr: $stderr\n" if $stderr ne ""; - } - return !$res; -} - sub updateRepository { my ($scm, $update, $scratchdir) = @_; my $curdir = getcwd; From adfe74b76a481deb4dbbf773129c5adde13cb6e9 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:25:42 -0500 Subject: [PATCH 04/10] HydraTestContext: give a helper for creating a project, jobset, evaluating jobs, and optionally building them. In return, get a hash of all the build records. --- t/lib/HydraTestContext.pm | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/t/lib/HydraTestContext.pm b/t/lib/HydraTestContext.pm index 6d15aaa1..19d1df11 100644 --- a/t/lib/HydraTestContext.pm +++ b/t/lib/HydraTestContext.pm @@ -5,6 +5,7 @@ package HydraTestContext; use File::Path qw(make_path); use File::Basename; use Cwd qw(abs_path getcwd); +use CliRunners; # Set up the environment for running tests. # @@ -114,6 +115,58 @@ sub jobsdir { return $self->{jobsdir}; } +# Create a jobset, evaluate it, and optionally build the jobs. +# +# In return, you get a hash of all the Builds records, keyed +# by their Nix attribute name. +# +# This always uses an `expression` from the `jobsdir` directory. +# +# Hash Parameters: +# +# * expression: The file in the jobsdir directory to evaluate +# * build: Bool. Attempt to build all the resulting jobs. Default: false. +sub makeAndEvaluateJobset { + my ($self, %opts) = @_; + + my $expression = $opts{'expression'} || die "Mandatory 'expression' option not passed to makeAndEValuateJobset."; + my $should_build = $opts{'build'} // 0; + + + # Create a new project for this test + my $project = $self->db()->resultset('Projects')->create({ + name => rand_chars(), + displayname => rand_chars(), + owner => "root" + }); + + # Create a new jobset for this test and set up the inputs + my $jobset = $project->jobsets->create({ + name => rand_chars(), + nixexprinput => "jobs", + nixexprpath => $expression, + emailoverride => "" + }); + my $jobsetinput = $jobset->jobsetinputs->create({name => "jobs", type => "path"}); + $jobsetinput->jobsetinputalts->create({altnr => 0, value => $self->jobsdir}); + + evalSucceeds($jobset) or die "Evaluating jobs/$expression should exit with return code 0"; + + my $builds = {}; + + for my $build ($jobset->builds) { + if ($should_build) { + runBuild($build) or die "Build '".$build->job."' from jobs/$expression should exit with return code 0"; + $build->discard_changes(); + } + + $builds->{$build->job} = $build; + } + + return $builds; +} + + sub DESTROY { my ($self) = @_; @@ -128,4 +181,8 @@ sub write_file { close $fh; } +sub rand_chars { + return sprintf("%08X", rand(0xFFFFFFFF)); +} + 1; From 7333d444c6a107d5ba422388c296b6f9514f0f13 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:25:54 -0500 Subject: [PATCH 05/10] evaluate-basic.t: move to makeAndEvaluateJobset --- t/evaluate-basic.t | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/t/evaluate-basic.t b/t/evaluate-basic.t index d3f21564..2e96930d 100644 --- a/t/evaluate-basic.t +++ b/t/evaluate-basic.t @@ -5,22 +5,31 @@ use Setup; use Test2::V0; my $ctx = test_context(); -my $db = $ctx->db; -my $project = $db->resultset('Projects')->create({name => "tests", displayname => "", owner => "root"}); +my $builds = $ctx->makeAndEvaluateJobset( + expression => "basic.nix", + build => 1 +); -# Most basic test case, no parameters -my $jobset = createBaseJobset("basic", "basic.nix", $ctx->jobsdir); +subtest "Build: succeed_with_failed" => sub { + my $build = $builds->{"succeed_with_failed"}; -ok(evalSucceeds($jobset), "Evaluating jobs/basic.nix should exit with return code 0"); -is(nrQueuedBuildsForJobset($jobset), 3, "Evaluating jobs/basic.nix should result in 3 builds"); + is($build->finished, 1, "Build should be finished."); + is($build->buildstatus, 6, "succeeeded-but-failed should have buildstatus 6."); +}; -for my $build (queuedBuildsForJobset($jobset)) { - ok(runBuild($build), "Build '".$build->job."' from jobs/basic.nix should exit with return code 0"); - my $newbuild = $db->resultset('Builds')->find($build->id); - is($newbuild->finished, 1, "Build '".$build->job."' from jobs/basic.nix should be finished."); - my $expected = $build->job eq "fails" ? 1 : $build->job =~ /with_failed/ ? 6 : 0; - is($newbuild->buildstatus, $expected, "Build '".$build->job."' from jobs/basic.nix should have buildstatus $expected."); -} +subtest "Build: empty_dir" => sub { + my $build = $builds->{"empty_dir"}; + + is($build->finished, 1, "Build should be finished."); + is($build->buildstatus, 0, "Should have succeeded."); +}; + +subtest "Build: fails" => sub { + my $build = $builds->{"fails"}; + + is($build->finished, 1, "Build should be finished."); + is($build->buildstatus, 1, "Should have failed."); +}; done_testing; From 008321d97295d2d7d5c0b8410fec6489ab5e9c26 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:32:13 -0500 Subject: [PATCH 06/10] build-products: switch to makeAndEvaluateJobset --- t/build-products.t | 47 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/t/build-products.t b/t/build-products.t index 3bc62d2e..34ecea5a 100644 --- a/t/build-products.t +++ b/t/build-products.t @@ -1,43 +1,36 @@ use strict; use warnings; use Setup; - -my %ctx = test_init(); - -require Hydra::Schema; -require Hydra::Model::DB; - use Test2::V0; -my $db = Hydra::Model::DB->new; -hydra_setup($db); - +my $ctx = test_context(); # Test build products -my $jobset = createBaseJobset("build-products", "build-products.nix", $ctx{jobsdir}); +my $builds = $ctx->makeAndEvaluateJobset( + expression => "build-products.nix", + build => 1 +); -ok(evalSucceeds($jobset), "Evaluating jobs/build-products.nix should exit with return code 0"); -is(nrQueuedBuildsForJobset($jobset), 2, "Evaluating jobs/build-products.nix should result in 2 builds"); +subtest "For the build job 'simple'" => sub { + my $build = $builds->{"simple"}; -for my $build (queuedBuildsForJobset($jobset)) { - subtest "For the build job '" . $build->job . "'" => sub { - ok(runBuild($build), "Build should exit with return code 0"); - my $newbuild = $db->resultset('Builds')->find($build->id); + is($build->finished, 1, "Build should have finished"); + is($build->buildstatus, 0, "Build should have buildstatus 0"); - is($newbuild->finished, 1, "Build should have finished"); - is($newbuild->buildstatus, 0, "Build should have buildstatus 0"); + my $buildproduct = $build->buildproducts->next; + is($buildproduct->name, "text.txt", "We should have \"text.txt\""); +}; - my $buildproducts = $db->resultset('BuildProducts')->search({ build => $build->id }); - my $buildproduct = $buildproducts->next; +subtest "For the build job 'with_spaces'" => sub { + my $build = $builds->{"with_spaces"}; - if($build->job eq "simple") { - is($buildproduct->name, "text.txt", "We should have \"text.txt\""); - } elsif ($build->job eq "with_spaces") { - is($buildproduct->name, "some text.txt", "We should have: \"some text.txt\""); - } - }; + is($build->finished, 1, "Build should have finished"); + is($build->buildstatus, 0, "Build should have buildstatus 0"); + + my $buildproduct = $build->buildproducts->next; + is($buildproduct->name, "some text.txt", "We should have: \"some text.txt\""); +}; -} done_testing; From c2384a04d893eec02c647ec887855435fd6eac3b Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:41:21 -0500 Subject: [PATCH 07/10] notifications.t: move to makeAndEvaluateJobset --- t/queue-runner/notifications.t | 57 +++++++++++++--------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/t/queue-runner/notifications.t b/t/queue-runner/notifications.t index 83646460..24481a1d 100644 --- a/t/queue-runner/notifications.t +++ b/t/queue-runner/notifications.t @@ -1,12 +1,12 @@ -use feature 'unicode_strings'; use strict; use warnings; use JSON::MaybeXS; use Setup; +use Test2::V0; my $binarycachedir = File::Temp->newdir(); -my %ctx = test_init( +my $ctx = test_context( nix_config => qq| experimental-features = nix-command substituters = file://${binarycachedir}?trusted=1 @@ -18,13 +18,9 @@ my %ctx = test_init( |); -require Hydra::Schema; -require Hydra::Model::DB; - -use Test2::V0; # Check that hydra's queue runner sends notifications. -# +# # The prelude to the test prebuilds one attribute and puts it in a # binary cache. The jobset will try to build that job plus another, # and we'll be able to check the behavior in both cases. @@ -40,11 +36,11 @@ subtest "Pre-build the job, upload to the cache, and then delete locally" => sub my $scratchlogdir = File::Temp->newdir(); $ENV{'NIX_LOG_DIR'} = "$scratchlogdir"; - my $outlink = "$ctx{tmpdir}/basic-canbesubstituted"; - is(system("nix-build '${ctx{jobsdir}}/notifications.nix' -A canbesubstituted --out-link '${outlink}'"), 0, "Building notifications.nix succeeded"); + my $outlink = $ctx->tmpdir . "/basic-canbesubstituted"; + is(system("nix-build '" . $ctx->jobsdir . "/notifications.nix' -A canbesubstituted --out-link '${outlink}'"), 0, "Building notifications.nix succeeded"); is(system("nix copy --to 'file://${binarycachedir}' '${outlink}'"), 0, "Copying the closure to the binary cache succeeded"); my $outpath = readlink($outlink); - + # Delete the store path and all of the system's garbage is(unlink($outlink), 1, "Deleting the GC root succeeds"); is(system("nix log '$outpath'"), 0, "Reading the output's log succeeds"); @@ -54,8 +50,8 @@ subtest "Pre-build the job, upload to the cache, and then delete locally" => sub subtest "Ensure substituting the job works, but reading the log fails" => sub { # Build the store path, with --max-jobs 0 to prevent builds - my $outlink = "$ctx{tmpdir}/basic-canbesubstituted"; - is(system("nix-build '${ctx{jobsdir}}/notifications.nix' -A canbesubstituted --max-jobs 0 --out-link '${outlink}'"), 0, "Building notifications.nix succeeded"); + my $outlink = $ctx->tmpdir . "/basic-canbesubstituted"; + is(system("nix-build '" . $ctx->jobsdir . "/notifications.nix' -A canbesubstituted --max-jobs 0 --out-link '${outlink}'"), 0, "Building notifications.nix succeeded"); my $outpath = readlink($outlink); # Verify trying to read this path's log fails, since we substituted it @@ -68,35 +64,27 @@ subtest "Ensure substituting the job works, but reading the log fails" => sub { is(system("nix-collect-garbage"), 0, "Delete all the system's garbage"); }; -my $db = Hydra::Model::DB->new; -hydra_setup($db); - -my $jobset = createBaseJobset("queue-runner-notifs", "notifications.nix", $ctx{jobsdir}); +my $db = $ctx->db(); my $dbh = $db->storage->dbh; $dbh->do("listen build_started"); $dbh->do("listen build_finished"); $dbh->do("listen step_finished"); -subtest "Evaluation of the jobset" => sub { - ok(evalSucceeds($jobset), "Evaluation should exit with return code 0"); - is(nrQueuedBuildsForJobset($jobset), 2, "Evaluation should result in 2 builds"); -}; - -my @builds = queuedBuildsForJobset($jobset); - +my $builds = $ctx->makeAndEvaluateJobset( + expression => "notifications.nix", + build => 1 +); subtest "Build: substitutable, canbesubstituted" => sub { - my ($build) = grep { $_->nixname eq "can-be-substituted" } @builds; - ok(runBuild($build), "Build should exit with return code 0"); + my $build = $builds->{"canbesubstituted"}; - my $newbuild = $db->resultset('Builds')->find($build->id); - is($newbuild->finished, 1, "Build should be finished."); - is($newbuild->buildstatus, 0, "Build should have buildstatus 0."); + is($build->finished, 1, "Build should be finished."); + is($build->buildstatus, 0, "Build should have buildstatus 0."); # Verify that hydra-notify will process this job, even if hydra-notify isn't # running at the time. - isnt($newbuild->notificationpendingsince, undef, "The build has a pending notification"); + isnt($build->notificationpendingsince, undef, "The build has a pending notification"); subtest "First notification: build_finished" => sub { my ($channelName, $pid, $payload) = @{$dbh->func("pg_notifies")}; @@ -106,16 +94,13 @@ subtest "Build: substitutable, canbesubstituted" => sub { }; subtest "Build: not substitutable, unsubstitutable" => sub { - my ($build) = grep { $_->nixname eq "unsubstitutable" } @builds; - ok(runBuild($build), "Build should exit with return code 0"); - - my $newbuild = $db->resultset('Builds')->find($build->id); - is($newbuild->finished, 1, "Build should be finished."); - is($newbuild->buildstatus, 0, "Build should have buildstatus 0."); + my $build = $builds->{"unsubstitutable"}; + is($build->finished, 1, "Build should be finished."); + is($build->buildstatus, 0, "Build should have buildstatus 0."); # Verify that hydra-notify will process this job, even if hydra-notify isn't # running at the time. - isnt($newbuild->notificationpendingsince, undef, "The build has a pending notification"); + isnt($build->notificationpendingsince, undef, "The build has a pending notification"); subtest "First notification: build_started" => sub { my ($channelName, $pid, $payload) = @{$dbh->func("pg_notifies")}; From 5836bc9a1192053cfce6addc19c475039dc320a7 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:48:07 -0500 Subject: [PATCH 08/10] HydraTestContext::makeAndEvaluateJobset: make a random user --- t/lib/HydraTestContext.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/t/lib/HydraTestContext.pm b/t/lib/HydraTestContext.pm index 19d1df11..38a26ef6 100644 --- a/t/lib/HydraTestContext.pm +++ b/t/lib/HydraTestContext.pm @@ -133,11 +133,18 @@ sub makeAndEvaluateJobset { my $should_build = $opts{'build'} // 0; + # Create a new user for this test + my $user = $self->db()->resultset('Users')->create({ + username => rand_chars(), + emailaddress => rand_chars() . '@example.org', + password => '' + }); + # Create a new project for this test my $project = $self->db()->resultset('Projects')->create({ name => rand_chars(), displayname => rand_chars(), - owner => "root" + owner => $user->username }); # Create a new jobset for this test and set up the inputs From 06f824ca487f423c08cb92e5d13dc83158500f7f Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:48:19 -0500 Subject: [PATCH 09/10] notifications.t: use system() with lists --- t/queue-runner/notifications.t | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/t/queue-runner/notifications.t b/t/queue-runner/notifications.t index 24481a1d..b35b2b2f 100644 --- a/t/queue-runner/notifications.t +++ b/t/queue-runner/notifications.t @@ -37,30 +37,30 @@ subtest "Pre-build the job, upload to the cache, and then delete locally" => sub $ENV{'NIX_LOG_DIR'} = "$scratchlogdir"; my $outlink = $ctx->tmpdir . "/basic-canbesubstituted"; - is(system("nix-build '" . $ctx->jobsdir . "/notifications.nix' -A canbesubstituted --out-link '${outlink}'"), 0, "Building notifications.nix succeeded"); - is(system("nix copy --to 'file://${binarycachedir}' '${outlink}'"), 0, "Copying the closure to the binary cache succeeded"); + is(system('nix-build', $ctx->jobsdir . '/notifications.nix', '-A', 'canbesubstituted', '--out-link', $outlink), 0, "Building notifications.nix succeeded"); + is(system('nix', 'copy', '--to', "file://${binarycachedir}", $outlink), 0, "Copying the closure to the binary cache succeeded"); my $outpath = readlink($outlink); # Delete the store path and all of the system's garbage is(unlink($outlink), 1, "Deleting the GC root succeeds"); - is(system("nix log '$outpath'"), 0, "Reading the output's log succeeds"); - is(system("nix-store --delete '$outpath'"), 0, "Deleting the notifications.nix output succeeded"); + is(system('nix', 'log', $outpath), 0, "Reading the output's log succeeds"); + is(system('nix-store', '--delete', $outpath), 0, "Deleting the notifications.nix output succeeded"); is(system("nix-collect-garbage"), 0, "Delete all the system's garbage"); }; subtest "Ensure substituting the job works, but reading the log fails" => sub { # Build the store path, with --max-jobs 0 to prevent builds my $outlink = $ctx->tmpdir . "/basic-canbesubstituted"; - is(system("nix-build '" . $ctx->jobsdir . "/notifications.nix' -A canbesubstituted --max-jobs 0 --out-link '${outlink}'"), 0, "Building notifications.nix succeeded"); + is(system('nix-build', $ctx->jobsdir . '/notifications.nix', '-A', 'canbesubstituted', '--max-jobs', '0', '--out-link', $outlink), 0, "Building notifications.nix succeeded"); my $outpath = readlink($outlink); # Verify trying to read this path's log fails, since we substituted it - isnt(system("nix log '$outpath'"), 0, "Reading the deleted output's log fails"); + isnt(system('nix', 'log', $outpath), 0, "Reading the deleted output's log fails"); # Delete the store path again and all of the store's garbage, ensuring # Hydra will try to build it. is(unlink($outlink), 1, "Deleting the GC root succeeds"); - is(system("nix-store --delete '$outpath'"), 0, "Deleting the basic output succeeded"); + is(system('nix-store', '--delete', $outpath), 0, "Deleting the notifications.nix output succeeded"); is(system("nix-collect-garbage"), 0, "Delete all the system's garbage"); }; From fbce3b6ed1fa9f136ec53729c81449f1ae84aa4e Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 20:52:40 -0500 Subject: [PATCH 10/10] default-machine-file: use makeAndEvaluateJobset --- t/queue-runner/default-machine-file.t | 31 ++++++++------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/t/queue-runner/default-machine-file.t b/t/queue-runner/default-machine-file.t index e6348226..ae38938b 100644 --- a/t/queue-runner/default-machine-file.t +++ b/t/queue-runner/default-machine-file.t @@ -2,34 +2,21 @@ use feature 'unicode_strings'; use strict; use warnings; use Setup; +use Test2::V0; -my %ctx = test_init( +my $ctx = test_context( nix_config => q| system-features = test-system-feature | ); -require Hydra::Schema; -require Hydra::Model::DB; +my $builds = $ctx->makeAndEvaluateJobset( + expression => "default-machine-file.nix", + build => 1, +); -use Test2::V0; - -my $db = Hydra::Model::DB->new; -hydra_setup($db); - -my $project = $db->resultset('Projects')->create({name => "tests", displayname => "", owner => "root"}); - -my $jobset = createBaseJobset("default-machine-file", "default-machine-file.nix", $ctx{jobsdir}); - -ok(evalSucceeds($jobset), "Evaluating jobs/default-machine-file.nix should exit with return code 0"); -is(nrQueuedBuildsForJobset($jobset), 1, "Evaluating jobs/default-machine-file.nix should result in 1 build"); - -for my $build (queuedBuildsForJobset($jobset)) { - ok(runBuild($build), "Build '".$build->job."' from jobs/default-machine-file.nix should exit with return code 0"); - my $newbuild = $db->resultset('Builds')->find($build->id); - is($newbuild->finished, 1, "Build '".$build->job."' from jobs/default-machine-file.nix should be finished."); - my $expected = $build->job eq "fails" ? 1 : $build->job =~ /with_failed/ ? 6 : 0; - is($newbuild->buildstatus, $expected, "Build '".$build->job."' from jobs/default-machine-file.nix should have buildstatus $expected."); -} +my $build = $builds->{"requireExperimentalFeatures"}; +is($build->finished, 1, "Build should be finished."); +is($build->buildstatus, 0, "Build status should be zero"); done_testing;