Skip to content

Commit b2fe3f5

Browse files
authored
Merge pull request #1455 from qowoz/226-constituent
nix-eval-jobs + constituent globs
2 parents d2db3c7 + 9911f01 commit b2fe3f5

12 files changed

Lines changed: 359 additions & 28 deletions

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nixos-modules/default.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
systemd.services.hydra-send-stats.enable = false;
1616

1717
services.postgresql.enable = true;
18-
services.postgresql.package = pkgs.postgresql_12;
1918

2019
# The following is to work around the following error from hydra-server:
2120
# [error] Caught exception in engine "Cannot determine local time zone"

t/evaluator/evaluate-constituents-broken.t

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,55 @@ use Hydra::Helper::Exec;
66

77
my $ctx = test_context();
88

9-
my $jobsetCtx = $ctx->makeJobset(
10-
expression => 'constituents-broken.nix',
11-
);
12-
my $jobset = $jobsetCtx->{"jobset"};
13-
14-
my ($res, $stdout, $stderr) = captureStdoutStderr(60,
15-
("hydra-eval-jobset", $jobsetCtx->{"project"}->name, $jobset->name)
16-
);
17-
isnt($res, 0, "hydra-eval-jobset exits non-zero");
18-
ok(utf8::decode($stderr), "Stderr output is UTF8-clean");
19-
like(
20-
$stderr,
21-
qr/aggregate job ‘mixed_aggregate’ failed with the error: "constituentA": does not exist/,
22-
"The stderr record includes a relevant error message"
23-
);
24-
25-
$jobset->discard_changes({ '+columns' => {'errormsg' => 'errormsg'} }); # refresh from DB
26-
like(
27-
$jobset->errormsg,
28-
qr/aggregate job ‘mixed_aggregate’ failed with the error: "constituentA": does not exist/,
29-
"The jobset records a relevant error message"
30-
);
9+
subtest "broken constituents expression" => sub {
10+
my $jobsetCtx = $ctx->makeJobset(
11+
expression => 'constituents-broken.nix',
12+
);
13+
my $jobset = $jobsetCtx->{"jobset"};
14+
15+
my ($res, $stdout, $stderr) = captureStdoutStderr(60,
16+
("hydra-eval-jobset", $jobsetCtx->{"project"}->name, $jobset->name)
17+
);
18+
isnt($res, 0, "hydra-eval-jobset exits non-zero");
19+
ok(utf8::decode($stderr), "Stderr output is UTF8-clean");
20+
like(
21+
$stderr,
22+
qr/aggregate job 'mixed_aggregate' references non-existent job 'constituentA'/,
23+
"The stderr record includes a relevant error message"
24+
);
25+
26+
$jobset->discard_changes({ '+columns' => {'errormsg' => 'errormsg'} }); # refresh from DB
27+
like(
28+
$jobset->errormsg,
29+
qr/aggregate job ‘mixed_aggregate’ failed with the error: constituentA: does not exist/,
30+
"The jobset records a relevant error message"
31+
);
32+
};
33+
34+
subtest "no matches" => sub {
35+
my $jobsetCtx = $ctx->makeJobset(
36+
expression => 'constituents-no-matches.nix',
37+
);
38+
my $jobset = $jobsetCtx->{"jobset"};
39+
40+
my ($res, $stdout, $stderr) = captureStdoutStderr(60,
41+
("hydra-eval-jobset", $jobsetCtx->{"project"}->name, $jobset->name)
42+
);
43+
isnt($res, 0, "hydra-eval-jobset exits non-zero");
44+
ok(utf8::decode($stderr), "Stderr output is UTF8-clean");
45+
like(
46+
$stderr,
47+
qr/aggregate job 'non_match_aggregate' references constituent glob pattern 'tests\.\*' with no matches/,
48+
"The stderr record includes a relevant error message"
49+
);
50+
51+
$jobset->discard_changes({ '+columns' => {'errormsg' => 'errormsg'} }); # refresh from DB
52+
like(
53+
$jobset->errormsg,
54+
qr/aggregate job ‘non_match_aggregate’ failed with the error: tests\.\*: constituent glob pattern had no matches/,
55+
qr/in job ‘non_match_aggregate’:\ntests\.\*: constituent glob pattern had no matches/,
56+
"The jobset records a relevant error message"
57+
);
58+
};
3159

3260
done_testing;
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
use strict;
2+
use warnings;
3+
use Setup;
4+
use Test2::V0;
5+
use Hydra::Helper::Exec;
6+
use Data::Dumper;
7+
8+
my $ctx = test_context();
9+
10+
subtest "general glob testing" => sub {
11+
my $jobsetCtx = $ctx->makeJobset(
12+
expression => 'constituents-glob.nix',
13+
);
14+
my $jobset = $jobsetCtx->{"jobset"};
15+
16+
my ($res, $stdout, $stderr) = captureStdoutStderr(60,
17+
("hydra-eval-jobset", $jobsetCtx->{"project"}->name, $jobset->name)
18+
);
19+
is($res, 0, "hydra-eval-jobset exits zero");
20+
21+
my $builds = {};
22+
for my $build ($jobset->builds) {
23+
$builds->{$build->job} = $build;
24+
}
25+
26+
subtest "basic globbing works" => sub {
27+
ok(defined $builds->{"ok_aggregate"}, "'ok_aggregate' is part of the jobset evaluation");
28+
my @constituents = $builds->{"ok_aggregate"}->constituents->all;
29+
is(2, scalar @constituents, "'ok_aggregate' has two constituents");
30+
31+
my @sortedConstituentNames = sort (map { $_->nixname } @constituents);
32+
33+
is($sortedConstituentNames[0], "empty-dir-A", "first constituent of 'ok_aggregate' is 'empty-dir-A'");
34+
is($sortedConstituentNames[1], "empty-dir-B", "second constituent of 'ok_aggregate' is 'empty-dir-B'");
35+
};
36+
37+
subtest "transitivity is OK" => sub {
38+
ok(defined $builds->{"indirect_aggregate"}, "'indirect_aggregate' is part of the jobset evaluation");
39+
my @constituents = $builds->{"indirect_aggregate"}->constituents->all;
40+
is(1, scalar @constituents, "'indirect_aggregate' has one constituent");
41+
is($constituents[0]->nixname, "direct_aggregate", "'indirect_aggregate' has 'direct_aggregate' as single constituent");
42+
};
43+
};
44+
45+
subtest "* selects all except current aggregate" => sub {
46+
my $jobsetCtx = $ctx->makeJobset(
47+
expression => 'constituents-glob-all.nix',
48+
);
49+
my $jobset = $jobsetCtx->{"jobset"};
50+
51+
my ($res, $stdout, $stderr) = captureStdoutStderr(60,
52+
("hydra-eval-jobset", $jobsetCtx->{"project"}->name, $jobset->name)
53+
);
54+
55+
subtest "no eval errors" => sub {
56+
ok(utf8::decode($stderr), "Stderr output is UTF8-clean");
57+
ok(
58+
$stderr !~ "aggregate job ‘ok_aggregate’ has a constituent .* that doesn't correspond to a Hydra build",
59+
"Catchall wildcard must not select itself as constituent"
60+
);
61+
62+
$jobset->discard_changes; # refresh from DB
63+
is(
64+
$jobset->errormsg,
65+
"",
66+
"eval-errors non-empty"
67+
);
68+
};
69+
70+
my $builds = {};
71+
for my $build ($jobset->builds) {
72+
$builds->{$build->job} = $build;
73+
}
74+
75+
subtest "two constituents" => sub {
76+
ok(defined $builds->{"ok_aggregate"}, "'ok_aggregate' is part of the jobset evaluation");
77+
my @constituents = $builds->{"ok_aggregate"}->constituents->all;
78+
is(2, scalar @constituents, "'ok_aggregate' has two constituents");
79+
80+
my @sortedConstituentNames = sort (map { $_->nixname } @constituents);
81+
82+
is($sortedConstituentNames[0], "empty-dir-A", "first constituent of 'ok_aggregate' is 'empty-dir-A'");
83+
is($sortedConstituentNames[1], "empty-dir-B", "second constituent of 'ok_aggregate' is 'empty-dir-B'");
84+
};
85+
};
86+
87+
subtest "trivial cycle check" => sub {
88+
my $jobsetCtx = $ctx->makeJobset(
89+
expression => 'constituents-cycle.nix',
90+
);
91+
my $jobset = $jobsetCtx->{"jobset"};
92+
93+
my ($res, $stdout, $stderr) = captureStdoutStderr(60,
94+
("hydra-eval-jobset", $jobsetCtx->{"project"}->name, $jobset->name)
95+
);
96+
97+
ok(
98+
$stderr =~ "Found dependency cycle between jobs 'indirect_aggregate' and 'ok_aggregate'",
99+
"Dependency cycle error is on stderr"
100+
);
101+
102+
ok(utf8::decode($stderr), "Stderr output is UTF8-clean");
103+
104+
$jobset->discard_changes; # refresh from DB
105+
like(
106+
$jobset->errormsg,
107+
qr/Dependency cycle: indirect_aggregate <-> ok_aggregate/,
108+
"eval-errors non-empty"
109+
);
110+
111+
is(0, $jobset->builds->count, "No builds should be scheduled");
112+
};
113+
114+
subtest "cycle check with globbing" => sub {
115+
my $jobsetCtx = $ctx->makeJobset(
116+
expression => 'constituents-cycle-glob.nix',
117+
);
118+
my $jobset = $jobsetCtx->{"jobset"};
119+
120+
my ($res, $stdout, $stderr) = captureStdoutStderr(60,
121+
("hydra-eval-jobset", $jobsetCtx->{"project"}->name, $jobset->name)
122+
);
123+
124+
ok(utf8::decode($stderr), "Stderr output is UTF8-clean");
125+
126+
$jobset->discard_changes; # refresh from DB
127+
like(
128+
$jobset->errormsg,
129+
qr/aggregate job ‘indirect_aggregate’ failed with the error: Dependency cycle: indirect_aggregate <-> packages.constituentA/,
130+
"packages.constituentA error missing"
131+
);
132+
133+
# on this branch of Hydra, hydra-eval-jobset fails hard if an aggregate
134+
# job is broken.
135+
is(0, $jobset->builds->count, "Zero jobs are scheduled");
136+
};
137+
138+
done_testing;

t/jobs/config.nix

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
rec {
2+
path = "/nix/store/l9mg93sgx50y88p5rr6x1vib6j1rjsds-coreutils-9.1/bin";
3+
4+
mkDerivation = args:
5+
derivation ({
6+
system = builtins.currentSystem;
7+
PATH = path;
8+
} // args);
9+
mkContentAddressedDerivation = args: mkDerivation ({
10+
__contentAddressed = true;
11+
outputHashMode = "recursive";
12+
outputHashAlgo = "sha256";
13+
} // args);
14+
}

t/jobs/constituents-cycle-glob.nix

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
with import ./config.nix;
2+
{
3+
packages.constituentA = mkDerivation {
4+
name = "empty-dir-A";
5+
builder = ./empty-dir-builder.sh;
6+
_hydraAggregate = true;
7+
_hydraGlobConstituents = true;
8+
constituents = [ "*_aggregate" ];
9+
};
10+
11+
packages.constituentB = mkDerivation {
12+
name = "empty-dir-B";
13+
builder = ./empty-dir-builder.sh;
14+
};
15+
16+
ok_aggregate = mkDerivation {
17+
name = "direct_aggregate";
18+
_hydraAggregate = true;
19+
_hydraGlobConstituents = true;
20+
constituents = [
21+
"packages.*"
22+
];
23+
builder = ./empty-dir-builder.sh;
24+
};
25+
26+
indirect_aggregate = mkDerivation {
27+
name = "indirect_aggregate";
28+
_hydraAggregate = true;
29+
constituents = [
30+
"ok_aggregate"
31+
];
32+
builder = ./empty-dir-builder.sh;
33+
};
34+
}

t/jobs/constituents-cycle.nix

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
with import ./config.nix;
2+
{
3+
ok_aggregate = mkDerivation {
4+
name = "direct_aggregate";
5+
_hydraAggregate = true;
6+
_hydraGlobConstituents = true;
7+
constituents = [
8+
"indirect_aggregate"
9+
];
10+
builder = ./empty-dir-builder.sh;
11+
};
12+
13+
indirect_aggregate = mkDerivation {
14+
name = "indirect_aggregate";
15+
_hydraAggregate = true;
16+
constituents = [
17+
"ok_aggregate"
18+
];
19+
builder = ./empty-dir-builder.sh;
20+
};
21+
}

t/jobs/constituents-glob-all.nix

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
with import ./config.nix;
2+
{
3+
packages.constituentA = mkDerivation {
4+
name = "empty-dir-A";
5+
builder = ./empty-dir-builder.sh;
6+
};
7+
8+
packages.constituentB = mkDerivation {
9+
name = "empty-dir-B";
10+
builder = ./empty-dir-builder.sh;
11+
};
12+
13+
ok_aggregate = mkDerivation {
14+
name = "direct_aggregate";
15+
_hydraAggregate = true;
16+
_hydraGlobConstituents = true;
17+
constituents = [
18+
"*"
19+
];
20+
builder = ./empty-dir-builder.sh;
21+
};
22+
}

t/jobs/constituents-glob.nix

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
with import ./config.nix;
2+
{
3+
packages.constituentA = mkDerivation {
4+
name = "empty-dir-A";
5+
builder = ./empty-dir-builder.sh;
6+
};
7+
8+
packages.constituentB = mkDerivation {
9+
name = "empty-dir-B";
10+
builder = ./empty-dir-builder.sh;
11+
};
12+
13+
ok_aggregate = mkDerivation {
14+
name = "direct_aggregate";
15+
_hydraAggregate = true;
16+
_hydraGlobConstituents = true;
17+
constituents = [
18+
"packages.*"
19+
];
20+
builder = ./empty-dir-builder.sh;
21+
};
22+
23+
indirect_aggregate = mkDerivation {
24+
name = "indirect_aggregate";
25+
_hydraAggregate = true;
26+
constituents = [
27+
"ok_aggregate"
28+
];
29+
builder = ./empty-dir-builder.sh;
30+
};
31+
}

t/jobs/constituents-no-matches.nix

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
with import ./config.nix;
2+
{
3+
non_match_aggregate = mkDerivation {
4+
name = "mixed_aggregate";
5+
_hydraAggregate = true;
6+
_hydraGlobConstituents = true;
7+
constituents = [
8+
"tests.*"
9+
];
10+
builder = ./empty-dir-builder.sh;
11+
};
12+
13+
# Without a second job no jobset is attempted to be created
14+
# (the only job would be broken)
15+
# and thus the constituent validation is never reached.
16+
dummy = mkDerivation {
17+
name = "dummy";
18+
builder = ./empty-dir-builder.sh;
19+
};
20+
}

0 commit comments

Comments
 (0)