test-driver: support testing user units

It is quite complicated to test services using the test-driver when
declaring user services with `systemd.user.services` such as many
X11-based services like `xautolock.service`.

This change adds an optional `$user` parameter to each systemd-related
function in the test-driver and runs `systemctl --user` commands using
`su -l $user -c ...` and sets the `XDG_RUNTIME_DIR` variable
accordingly and a new function named `systemctl` which is able to run a
systemd command with or without a specified user.

The change can be confirmed with a simple VM declaration like this:

```
import ./nixos/tests/make-test.nix ({ pkgs, lib }:

with lib;

{
name = "systemd-user-test";

nodes.machine = {
imports = [ ./nixos/tests/common/user-account.nix ];

services.xserver.enable = true;
services.xserver.displayManager.auto.enable = true;
services.xserver.displayManager.auto.user = "bob";
services.xserver.xautolock.enable = true;
};

testScript = ''
$machine->start;
$machine->waitForX;

$machine->waitForUnit("xautolock.service", "bob");
$machine->stopJob("xautolock.service", "bob");
$machine->startJob("xautolock.service", "bob");
$machine->systemctl("list-jobs --no-pager", "bob");
$machine->systemctl("show 'xautolock.service' --no-pager", "bob");
'';
})
```

+19 -9
+19 -9
nixos/lib/test-driver/Machine.pm
··· 362 362 363 363 364 364 sub getUnitInfo { 365 - my ($self, $unit) = @_; 366 - my ($status, $lines) = $self->execute("systemctl --no-pager show '$unit'"); 365 + my ($self, $unit, $user) = @_; 366 + my ($status, $lines) = $self->systemctl("--no-pager show \"$unit\"", $user); 367 367 return undef if $status != 0; 368 368 my $info = {}; 369 369 foreach my $line (split '\n', $lines) { ··· 371 371 $info->{$1} = $2; 372 372 } 373 373 return $info; 374 + } 375 + 376 + sub systemctl { 377 + my ($self, $q, $user) = @_; 378 + if ($user) { 379 + $q =~ s/'/\\'/g; 380 + return $self->execute("su -l $user -c \$'XDG_RUNTIME_DIR=/run/user/`id -u` systemctl --user $q'"); 381 + } 382 + 383 + return $self->execute("systemctl $q"); 374 384 } 375 385 376 386 # Fail if the given systemd unit is not in the "active" state. ··· 387 397 388 398 # Wait for a systemd unit to reach the "active" state. 389 399 sub waitForUnit { 390 - my ($self, $unit) = @_; 400 + my ($self, $unit, $user) = @_; 391 401 $self->nest("waiting for unit ‘$unit’", sub { 392 402 retry sub { 393 - my $info = $self->getUnitInfo($unit); 403 + my $info = $self->getUnitInfo($unit, $user); 394 404 my $state = $info->{ActiveState}; 395 405 die "unit ‘$unit’ reached state ‘$state’\n" if $state eq "failed"; 396 406 if ($state eq "inactive") { 397 407 # If there are no pending jobs, then assume this unit 398 408 # will never reach active state. 399 - my ($status, $jobs) = $self->execute("systemctl list-jobs --full 2>&1"); 409 + my ($status, $jobs) = $self->systemctl("list-jobs --full 2>&1", $user); 400 410 if ($jobs =~ /No jobs/) { # FIXME: fragile 401 411 # Handle the case where the unit may have started 402 412 # between the previous getUnitInfo() and ··· 430 440 } 431 441 432 442 sub startJob { 433 - my ($self, $jobName) = @_; 434 - $self->execute("systemctl start $jobName"); 443 + my ($self, $jobName, $user) = @_; 444 + $self->systemctl("start $jobName", $user); 435 445 # FIXME: check result 436 446 } 437 447 438 448 sub stopJob { 439 - my ($self, $jobName) = @_; 440 - $self->execute("systemctl stop $jobName"); 449 + my ($self, $jobName, $user) = @_; 450 + $self->systemctl("stop $jobName", $user); 441 451 } 442 452 443 453