nix: simplify tests

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I02126573ee9573fd7a1e5a12e42dd02d6a6a6964
This commit is contained in:
raf 2026-02-18 18:30:52 +03:00
commit d541b7ebbf
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
4 changed files with 85 additions and 82 deletions

View file

@ -268,6 +268,14 @@ async fn evaluate_jobset(
evaluation jobset={} commit={}", evaluation jobset={} commit={}",
build_count, jobset.name, commit_hash build_count, jobset.name, commit_hash
); );
if let Err(e) =
repo::jobsets::update_last_checked(pool, jobset.id).await
{
tracing::warn!(
jobset = %jobset.name,
"Failed to update last_checked_at: {e}"
);
}
return Ok(()); return Ok(());
} else { } else {
info!( info!(

View file

@ -223,7 +223,7 @@ development.
| `cache` | `secret_key_file` | none | Signing key for binary cache | | `cache` | `secret_key_file` | none | Signing key for binary cache |
| `signing` | `enabled` | `false` | Sign build outputs | | `signing` | `enabled` | `false` | Sign build outputs |
| `signing` | `key_file` | none | Signing key file path | | `signing` | `key_file` | none | Signing key file path |
| `notifications` | `run_command` | none | Command to run on build completion | | `notifications` | `webhook_url` | none | HTTP endpoint to POST build status JSON |
| `notifications` | `github_token` | none | GitHub token for commit status updates | | `notifications` | `github_token` | none | GitHub token for commit status updates |
| `notifications` | `gitea_url` | none | Gitea/Forgejo instance URL | | `notifications` | `gitea_url` | none | Gitea/Forgejo instance URL |
| `notifications` | `gitea_token` | none | Gitea/Forgejo API token | | `notifications` | `gitea_token` | none | Gitea/Forgejo API token |

View file

@ -238,7 +238,7 @@
options = { options = {
notificationType = mkOption { notificationType = mkOption {
type = str; type = str;
description = "Notification type: github_status, email, gitlab_status, gitea_status, run_command."; description = "Notification type: github_status, email, gitlab_status, gitea_status, webhook.";
}; };
config = mkOption { config = mkOption {
type = settingsType; type = settingsType;

View file

@ -293,17 +293,19 @@ pkgs.testers.nixosTest {
f"{ro_header}" f"{ro_header}"
).strip() ).strip()
assert code == "403", f"Expected 403 for read-only input delete, got {code}" assert code == "403", f"Expected 403 for read-only input delete, got {code}"
# Clean up: admin deletes the temp input so it doesn't affect future
# inputs_hash computations and evaluator cache lookups
machine.succeed(
"curl -sf -o /dev/null "
f"-X DELETE http://127.0.0.1:3000/api/v1/projects/{e2e_project_id}/jobsets/{e2e_jobset_id}/inputs/{tmp_input_id} "
f"{auth_header}"
)
# Notifications are dispatched after builds complete (already tested above). with subtest("Build status is succeeded"):
# Verify run_command notifications work:
with subtest("Notification run_command is invoked on build completion"):
# This tests that the notification system dispatches properly.
# The actual run_command config is not set in this VM, so we just verify
# the build status was updated correctly after notification dispatch.
result = machine.succeed( result = machine.succeed(
f"curl -sf http://127.0.0.1:3000/api/v1/builds/{e2e_build_id} | jq -r .status" f"curl -sf http://127.0.0.1:3000/api/v1/builds/{e2e_build_id} | jq -r .status"
).strip() ).strip()
assert result == "succeeded", f"Expected succeeded after notification, got {result}" assert result == "succeeded", f"Expected succeeded, got {result}"
with subtest("Channel auto-promotion after all builds complete"): with subtest("Channel auto-promotion after all builds complete"):
# Create a channel tracking the E2E jobset # Create a channel tracking the E2E jobset
@ -388,34 +390,43 @@ pkgs.testers.nixosTest {
timeout=10 timeout=10
) )
with subtest("Notification run_command invoked on build completion"): with subtest("Webhook notification fires on build completion"):
# Write a notification script # Start a minimal HTTP server on the VM to receive the webhook POST.
machine.succeed("mkdir -p /var/lib/fc") # Writes the request body to /tmp/webhook.json so we can inspect it.
machine.succeed(""" machine.succeed(
cat > /var/lib/fc/notify.sh << 'SCRIPT' "cat > /tmp/webhook-server.py << 'PYEOF'\n"
#!/bin/sh "import http.server, json\n"
echo "BUILD_STATUS=$FC_BUILD_STATUS" >> /var/lib/fc/notify-output "class H(http.server.BaseHTTPRequestHandler):\n"
echo "BUILD_ID=$FC_BUILD_ID" >> /var/lib/fc/notify-output " def do_POST(self):\n"
echo "BUILD_JOB=$FC_BUILD_JOB" >> /var/lib/fc/notify-output " n = int(self.headers.get('Content-Length', 0))\n"
SCRIPT " body = self.rfile.read(n)\n"
""") " open('/tmp/webhook.json', 'wb').write(body)\n"
machine.succeed("chmod +x /var/lib/fc/notify.sh") " self.send_response(200)\n"
machine.succeed("chown -R fc:fc /var/lib/fc") " self.end_headers()\n"
" def log_message(self, *a): pass\n"
"http.server.HTTPServer(('127.0.0.1', 9998), H).serve_forever()\n"
"PYEOF\n"
)
machine.succeed("python3 /tmp/webhook-server.py &")
machine.wait_until_succeeds(
"curl -sf -X POST -H 'Content-Length: 2' -d '{}' http://127.0.0.1:9998/",
timeout=10
)
machine.succeed("rm -f /tmp/webhook.json")
# Enable notifications via systemd drop-in override (adds env var directly to service unit) # Configure queue-runner to send webhook notifications
machine.succeed("mkdir -p /run/systemd/system/fc-queue-runner.service.d") machine.succeed("mkdir -p /run/systemd/system/fc-queue-runner.service.d")
machine.succeed(""" machine.succeed(
cat > /run/systemd/system/fc-queue-runner.service.d/notify.conf << 'EOF' "cat > /run/systemd/system/fc-queue-runner.service.d/webhook.conf << 'EOF'\n"
[Service] "[Service]\n"
Environment=FC_NOTIFICATIONS__RUN_COMMAND=/var/lib/fc/notify.sh "Environment=FC_NOTIFICATIONS__WEBHOOK_URL=http://127.0.0.1:9998/notify\n"
EOF "EOF\n"
""") )
machine.succeed("systemctl daemon-reload") machine.succeed("systemctl daemon-reload")
machine.succeed("systemctl restart fc-queue-runner") machine.succeed("systemctl restart fc-queue-runner")
machine.wait_for_unit("fc-queue-runner.service", timeout=30) machine.wait_for_unit("fc-queue-runner.service", timeout=30)
# Create a new simple build to trigger notification # Push a new commit to trigger a fresh evaluation and build
# Push a trivial change to trigger a new evaluation
machine.succeed( machine.succeed(
"cd /tmp/test-flake-work && \\\n" "cd /tmp/test-flake-work && \\\n"
"cat > flake.nix << 'FLAKE'\n" "cat > flake.nix << 'FLAKE'\n"
@ -432,31 +443,16 @@ pkgs.testers.nixosTest {
"}\n" "}\n"
"FLAKE\n" "FLAKE\n"
) )
machine.succeed("cd /tmp/test-flake-work && git add -A && git commit -m 'trigger notification test'") machine.succeed("cd /tmp/test-flake-work && git add -A && git commit -m 'trigger webhook notification test'")
machine.succeed("cd /tmp/test-flake-work && git push origin HEAD:refs/heads/master") machine.succeed("cd /tmp/test-flake-work && git push origin HEAD:refs/heads/master")
# Wait for the notify-test build to succeed # Wait for the webhook to arrive (the build must complete first)
machine.wait_until_succeeds( machine.wait_until_succeeds("test -e /tmp/webhook.json", timeout=120)
"curl -sf 'http://127.0.0.1:3000/api/v1/builds?job_name=notify-test' " payload = json.loads(machine.succeed("cat /tmp/webhook.json"))
"| jq -e '.items[] | select(.status==\"succeeded\")'", assert payload["build_status"] == "success", \
timeout=120 f"Expected build_status=success in webhook payload, got: {payload}"
) assert "build_id" in payload, f"Missing build_id in webhook payload: {payload}"
assert "build_job" in payload, f"Missing build_job in webhook payload: {payload}"
# Get the build ID
notify_build_id = machine.succeed(
"curl -sf 'http://127.0.0.1:3000/api/v1/builds?job_name=notify-test' "
"| jq -r '.items[] | select(.status==\"succeeded\") | .id' | head -1"
).strip()
# Wait a bit for notification to dispatch
time.sleep(5)
# Verify the notification script was executed
machine.wait_for_file("/var/lib/fc/notify-output")
output = machine.succeed("cat /var/lib/fc/notify-output")
assert "BUILD_STATUS=success" in output, \
f"Expected BUILD_STATUS=success in notification output, got: {output}"
assert notify_build_id in output, f"Expected build ID {notify_build_id} in output, got: {output}"
with subtest("Generate signing key and configure signing"): with subtest("Generate signing key and configure signing"):
# Generate a Nix signing key # Generate a Nix signing key
@ -467,13 +463,13 @@ pkgs.testers.nixosTest {
# Enable signing via systemd drop-in override # Enable signing via systemd drop-in override
machine.succeed("mkdir -p /run/systemd/system/fc-queue-runner.service.d") machine.succeed("mkdir -p /run/systemd/system/fc-queue-runner.service.d")
machine.succeed(""" machine.succeed(
cat > /run/systemd/system/fc-queue-runner.service.d/signing.conf << 'EOF' "cat > /run/systemd/system/fc-queue-runner.service.d/signing.conf << 'EOF'\n"
[Service] "[Service]\n"
Environment=FC_SIGNING__ENABLED=true "Environment=FC_SIGNING__ENABLED=true\n"
Environment=FC_SIGNING__KEY_FILE=/var/lib/fc/keys/signing-key "Environment=FC_SIGNING__KEY_FILE=/var/lib/fc/keys/signing-key\n"
EOF "EOF\n"
""") )
machine.succeed("systemctl daemon-reload") machine.succeed("systemctl daemon-reload")
machine.succeed("systemctl restart fc-queue-runner") machine.succeed("systemctl restart fc-queue-runner")
machine.wait_for_unit("fc-queue-runner.service", timeout=30) machine.wait_for_unit("fc-queue-runner.service", timeout=30)
@ -530,15 +526,15 @@ pkgs.testers.nixosTest {
with subtest("GC roots are created for build products"): with subtest("GC roots are created for build products"):
# Enable GC via systemd drop-in override # Enable GC via systemd drop-in override
machine.succeed("mkdir -p /run/systemd/system/fc-queue-runner.service.d") machine.succeed("mkdir -p /run/systemd/system/fc-queue-runner.service.d")
machine.succeed(""" machine.succeed(
cat > /run/systemd/system/fc-queue-runner.service.d/gc.conf << 'EOF' "cat > /run/systemd/system/fc-queue-runner.service.d/gc.conf << 'EOF'\n"
[Service] "[Service]\n"
Environment=FC_GC__ENABLED=true "Environment=FC_GC__ENABLED=true\n"
Environment=FC_GC__GC_ROOTS_DIR=/nix/var/nix/gcroots/per-user/fc "Environment=FC_GC__GC_ROOTS_DIR=/nix/var/nix/gcroots/per-user/fc\n"
Environment=FC_GC__MAX_AGE_DAYS=30 "Environment=FC_GC__MAX_AGE_DAYS=30\n"
Environment=FC_GC__CLEANUP_INTERVAL=3600 "Environment=FC_GC__CLEANUP_INTERVAL=3600\n"
EOF "EOF\n"
""") )
machine.succeed("systemctl daemon-reload") machine.succeed("systemctl daemon-reload")
machine.succeed("systemctl restart fc-queue-runner") machine.succeed("systemctl restart fc-queue-runner")
machine.wait_for_unit("fc-queue-runner.service", timeout=30) machine.wait_for_unit("fc-queue-runner.service", timeout=30)
@ -599,7 +595,6 @@ pkgs.testers.nixosTest {
) )
# Wait for a symlink pointing to our build output to appear # Wait for a symlink pointing to our build output to appear
import time
found = False found = False
for _ in range(10): for _ in range(10):
if wait_for_gc_root(): if wait_for_gc_root():
@ -612,16 +607,16 @@ pkgs.testers.nixosTest {
with subtest("Declarative .fc.toml in repo auto-creates jobset"): with subtest("Declarative .fc.toml in repo auto-creates jobset"):
# Add .fc.toml to the test repo with a new jobset definition # Add .fc.toml to the test repo with a new jobset definition
machine.succeed(""" machine.succeed(
cd /tmp/test-flake-work && \ "cd /tmp/test-flake-work && "
cat > .fc.toml << 'FCTOML' "cat > .fc.toml << 'FCTOML'\n"
[[jobsets]] "[[jobsets]]\n"
name = "declarative-checks" 'name = "declarative-checks"\n'
nix_expression = "checks" 'nix_expression = "checks"\n'
flake_mode = true "flake_mode = true\n"
enabled = true "enabled = true\n"
FCTOML "FCTOML\n"
""") )
machine.succeed("cd /tmp/test-flake-work && git add -A && git commit -m 'add declarative config'") machine.succeed("cd /tmp/test-flake-work && git add -A && git commit -m 'add declarative config'")
machine.succeed("cd /tmp/test-flake-work && git push origin HEAD:refs/heads/master") machine.succeed("cd /tmp/test-flake-work && git push origin HEAD:refs/heads/master")