From 8aa59dcc1bf18cd331a6c0fb85f51a98fb3c1eef Mon Sep 17 00:00:00 2001
From: Graham Christensen <graham@floxdev.com>
Date: Wed, 14 Apr 2021 10:16:19 -0400
Subject: [PATCH 1/5] Drop references to NIX_MANFIESTS_DIR and NIX_BUILD_HOOK

Neither of these have been supported in ~years.
---
 t/Makefile.am  | 2 --
 t/lib/Setup.pm | 2 --
 2 files changed, 4 deletions(-)

diff --git a/t/Makefile.am b/t/Makefile.am
index 4f84154c..358f3698 100644
--- a/t/Makefile.am
+++ b/t/Makefile.am
@@ -8,10 +8,8 @@ TESTS_ENVIRONMENT = \
   NIX_REMOTE_SYSTEMS=					\
   NIX_CONF_DIR="$(abs_builddir)/nix/etc/nix"		\
   NIX_STATE_DIR="$(abs_builddir)/nix/var/nix"		\
-  NIX_MANIFESTS_DIR="$(abs_builddir)/nix/var/nix/manifests"	\
   NIX_STORE_DIR="$(abs_builddir)/nix/store"			\
   NIX_LOG_DIR="$(abs_builddir)/nix/var/log/nix"		\
-  NIX_BUILD_HOOK=					\
   PGHOST=/tmp	\
   PERL5LIB="$(srcdir):$(abs_top_srcdir)/src/lib:$$PERL5LIB"	\
   PYTHONPATH= \
diff --git a/t/lib/Setup.pm b/t/lib/Setup.pm
index cdea38ce..a246b653 100644
--- a/t/lib/Setup.pm
+++ b/t/lib/Setup.pm
@@ -56,8 +56,6 @@ sub test_init {
     close $fh;
 
     $ENV{'NIX_STATE_DIR'} = "$dir/nix/var/nix";
-
-    $ENV{'NIX_MANIFESTS_DIR'} = "$dir/nix/var/nix/manifests";
     $ENV{'NIX_STORE_DIR'} = "$dir/nix/store";
     $ENV{'NIX_LOG_DIR'} = "$dir/nix/var/log/nix";
 

From 74d34c0f80cfbd047d80bd45e06084511c405e1f Mon Sep 17 00:00:00 2001
From: Graham Christensen <graham@floxdev.com>
Date: Wed, 14 Apr 2021 10:17:21 -0400
Subject: [PATCH 2/5] t/Setup.pm: sort NIX_ env vars

---
 t/lib/Setup.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/lib/Setup.pm b/t/lib/Setup.pm
index a246b653..f87458dd 100644
--- a/t/lib/Setup.pm
+++ b/t/lib/Setup.pm
@@ -55,9 +55,9 @@ sub test_init {
     print $fh $opts{'hydra_config'} || "";
     close $fh;
 
+    $ENV{'NIX_LOG_DIR'} = "$dir/nix/var/log/nix";
     $ENV{'NIX_STATE_DIR'} = "$dir/nix/var/nix";
     $ENV{'NIX_STORE_DIR'} = "$dir/nix/store";
-    $ENV{'NIX_LOG_DIR'} = "$dir/nix/var/log/nix";
 
     my $pgsql = Test::PostgreSQL->new(
         extra_initdb_args => "--locale C.UTF-8"

From c7ac123dc5ed4b4d971a413bd342cc784bc4bac0 Mon Sep 17 00:00:00 2001
From: Graham Christensen <graham@floxdev.com>
Date: Wed, 14 Apr 2021 10:18:22 -0400
Subject: [PATCH 3/5] Setup.pm: specify NIX_ env vars for running yath

Otherwise yath will try to use global configuration.
---
 t/lib/Setup.pm | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/t/lib/Setup.pm b/t/lib/Setup.pm
index f87458dd..14655348 100644
--- a/t/lib/Setup.pm
+++ b/t/lib/Setup.pm
@@ -56,6 +56,8 @@ sub test_init {
     close $fh;
 
     $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";
 

From e45f852277ad8bbdf15cac2f8ddecd8a788b2e40 Mon Sep 17 00:00:00 2001
From: Graham Christensen <graham@floxdev.com>
Date: Wed, 14 Apr 2021 14:10:43 -0400
Subject: [PATCH 4/5] tests: allow specifying some nix config

---
 t/lib/Setup.pm | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/t/lib/Setup.pm b/t/lib/Setup.pm
index 14655348..b5236978 100644
--- a/t/lib/Setup.pm
+++ b/t/lib/Setup.pm
@@ -19,6 +19,7 @@ our @EXPORT = qw(test_init hydra_setup nrBuildsForJobset queuedBuildsForJobset
 # Hash Parameters:
 #
 #  * hydra_config: configuration for the Hydra processes for your test.
+#  * nix_config: text to include in the test's nix.conf
 #
 # This clears several environment variables and sets them to ephemeral
 # values: a temporary database, temporary Nix store, temporary Hydra
@@ -47,6 +48,7 @@ sub test_init {
     my $nixconf = "$ENV{'NIX_CONF_DIR'}/nix.conf";
     open(my $fh, '>', $nixconf) or die "Could not open file '$nixconf' $!";
     print $fh "sandbox = false\n";
+    print $fh $opts{'nix_config'} || "";
     close $fh;
 
     $ENV{'HYDRA_CONFIG'} = "$dir/hydra.conf";

From cf4434bc9feac643939ce5991d8259db07dd4199 Mon Sep 17 00:00:00 2001
From: Graham Christensen <graham@floxdev.com>
Date: Wed, 14 Apr 2021 14:11:37 -0400
Subject: [PATCH 5/5] queue runner: test notifications

Especially, test the difference in behavior of substituted and unsubstituted builds.
---
 t/jobs/notifications.nix       |  14 ++++
 t/queue-runner/notifications.t | 142 +++++++++++++++++++++++++++++++++
 2 files changed, 156 insertions(+)
 create mode 100644 t/jobs/notifications.nix
 create mode 100644 t/queue-runner/notifications.t

diff --git a/t/jobs/notifications.nix b/t/jobs/notifications.nix
new file mode 100644
index 00000000..dd60e6c5
--- /dev/null
+++ b/t/jobs/notifications.nix
@@ -0,0 +1,14 @@
+with import ./config.nix;
+{
+  canbesubstituted =
+    mkDerivation {
+      name = "can-be-substituted";
+      builder = ./empty-dir-builder.sh;
+    };
+    
+  unsubstitutable =
+    mkDerivation {
+      name = "unsubstitutable";
+      builder = ./empty-dir-builder.sh;
+    };
+}
diff --git a/t/queue-runner/notifications.t b/t/queue-runner/notifications.t
new file mode 100644
index 00000000..3c43bceb
--- /dev/null
+++ b/t/queue-runner/notifications.t
@@ -0,0 +1,142 @@
+use feature 'unicode_strings';
+use strict;
+use warnings;
+use JSON;
+use Setup;
+
+my $binarycachedir = File::Temp->newdir();
+
+my %ctx = test_init(
+    nix_config => qq|
+    experimental-features = nix-command
+    substituters = file://${binarycachedir}?trusted=1
+    |,
+    hydra_config => q|
+    use-substitutes = 1
+    <runcommand>
+      command = cp "$HYDRA_JSON" "$HYDRA_DATA/joboutput.json"
+    </runcommand>
+|);
+
+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.
+#
+# Our test checks that the queue runner sends notifications even when the
+# build it is performing can be substituted from a configured cache.
+# To replicate this behavior we need to build an exact match of the
+# derivation, upload it to a configured binary cache, then delete it
+# locally. For completeness, we also verify that we can substitute
+# the build locally.
+
+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");
+    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-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");
+    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");
+
+    # 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-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 $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);
+
+
+subtest "Build: substitutable, canbesubstituted" => sub {
+    my ($build) = grep { $_->nixname eq "can-be-substituted" } @builds;
+    ok(runBuild($build), "Build should exit with 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.");
+
+    # 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");
+
+    subtest "First notification: build_finished" => sub {
+        my ($channelName, $pid, $payload) = @{$dbh->func("pg_notifies")};
+        is($channelName, "build_finished", "The event is for the build finishing");
+        is($payload, $build->id, "The payload is the build's ID");
+    };
+};
+
+subtest "Build: not substitutable, unsubstitutable" => sub {
+    my ($build) = grep { $_->nixname eq "unsubstitutable" } @builds;
+    ok(runBuild($build), "Build should exit with 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.");
+
+    # 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");
+
+    subtest "First notification: build_started" => sub {
+        my ($channelName, $pid, $payload) = @{$dbh->func("pg_notifies")};
+        is($channelName, "build_started", "The event is for the build starting");
+        is($payload, $build->id, "The payload is the build's ID");
+    };
+
+    subtest "Second notification: step_finished" => sub {
+        my ($channelName, $pid, $payload) = @{$dbh->func("pg_notifies")};
+        is($channelName, "step_finished", "The event is for the step finishing");
+        my ($buildId, $stepNr, $logFile) = split "\t", $payload;
+        is($buildId, $build->id, "The payload is the build's ID");
+        is($stepNr, 1, "The payload is the build's step number");
+        isnt($logFile, undef, "The log file is passed");
+    };
+
+    subtest "Third notification: build_finished" => sub {
+        my ($channelName, $pid, $payload) = @{$dbh->func("pg_notifies")};
+        is($channelName, "build_finished", "The event is for the build finishing");
+        is($payload, $build->id, "The payload is the build's ID");
+    };
+};
+
+done_testing;