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");
  '';
})
```
This commit is contained in:
Maximilian Bosch 2017-12-19 15:19:38 +01:00
parent b256afac58
commit e538e00404
No known key found for this signature in database
GPG Key ID: 091DBF4D1FC46B8E

View File

@ -362,8 +362,8 @@ sub mustFail {
sub getUnitInfo { sub getUnitInfo {
my ($self, $unit) = @_; my ($self, $unit, $user) = @_;
my ($status, $lines) = $self->execute("systemctl --no-pager show '$unit'"); my ($status, $lines) = $self->systemctl("--no-pager show \"$unit\"", $user);
return undef if $status != 0; return undef if $status != 0;
my $info = {}; my $info = {};
foreach my $line (split '\n', $lines) { foreach my $line (split '\n', $lines) {
@ -373,6 +373,16 @@ sub getUnitInfo {
return $info; return $info;
} }
sub systemctl {
my ($self, $q, $user) = @_;
if ($user) {
$q =~ s/'/\\'/g;
return $self->execute("su -l $user -c \$'XDG_RUNTIME_DIR=/run/user/`id -u` systemctl --user $q'");
}
return $self->execute("systemctl $q");
}
# Fail if the given systemd unit is not in the "active" state. # Fail if the given systemd unit is not in the "active" state.
sub requireActiveUnit { sub requireActiveUnit {
my ($self, $unit) = @_; my ($self, $unit) = @_;
@ -387,16 +397,16 @@ sub requireActiveUnit {
# Wait for a systemd unit to reach the "active" state. # Wait for a systemd unit to reach the "active" state.
sub waitForUnit { sub waitForUnit {
my ($self, $unit) = @_; my ($self, $unit, $user) = @_;
$self->nest("waiting for unit $unit", sub { $self->nest("waiting for unit $unit", sub {
retry sub { retry sub {
my $info = $self->getUnitInfo($unit); my $info = $self->getUnitInfo($unit, $user);
my $state = $info->{ActiveState}; my $state = $info->{ActiveState};
die "unit $unit reached state $state\n" if $state eq "failed"; die "unit $unit reached state $state\n" if $state eq "failed";
if ($state eq "inactive") { if ($state eq "inactive") {
# If there are no pending jobs, then assume this unit # If there are no pending jobs, then assume this unit
# will never reach active state. # will never reach active state.
my ($status, $jobs) = $self->execute("systemctl list-jobs --full 2>&1"); my ($status, $jobs) = $self->systemctl("list-jobs --full 2>&1", $user);
if ($jobs =~ /No jobs/) { # FIXME: fragile if ($jobs =~ /No jobs/) { # FIXME: fragile
# Handle the case where the unit may have started # Handle the case where the unit may have started
# between the previous getUnitInfo() and # between the previous getUnitInfo() and
@ -430,14 +440,14 @@ sub waitForFile {
} }
sub startJob { sub startJob {
my ($self, $jobName) = @_; my ($self, $jobName, $user) = @_;
$self->execute("systemctl start $jobName"); $self->systemctl("start $jobName", $user);
# FIXME: check result # FIXME: check result
} }
sub stopJob { sub stopJob {
my ($self, $jobName) = @_; my ($self, $jobName, $user) = @_;
$self->execute("systemctl stop $jobName"); $self->systemctl("stop $jobName", $user);
} }