// Copyright (C) 2021 Simon Ruderich // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package sync import ( "bytes" "fmt" "os/exec" "testing" "ruderich.org/simon/safcm" "ruderich.org/simon/safcm/testutil" ) func TestSyncServicesSystemd(t *testing.T) { tests := []struct { name string req safcm.MsgSyncReq stdout [][]byte stderr [][]byte errors []error expCmds []*exec.Cmd expDbg []string expResp safcm.MsgSyncResp expErr error }{ // NOTE: Also update MsgSyncResp in safcm test cases when // changing the MsgSyncResp struct! { "no service change necessary", safcm.MsgSyncReq{ Services: []string{ "service-one", "service-two", }, }, [][]byte{ []byte(`ActiveState=active UnitFileState=enabled LoadError= ActiveState=active UnitFileState=enabled LoadError= `), }, [][]byte{nil}, []error{nil}, []*exec.Cmd{&exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "show", "--property=ActiveState,UnitFileState,LoadError", "--", "service-one", "service-two", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }}, []string{ "4: sync remote: services: checking service-one service-two (systemd detected)", `4: sync remote: services: running "/bin/systemctl" "show" "--property=ActiveState,UnitFileState,LoadError" "--" "service-one" "service-two"`, `5: sync remote: services: command stdout: ActiveState=active UnitFileState=enabled LoadError= ActiveState=active UnitFileState=enabled LoadError= `, }, safcm.MsgSyncResp{}, nil, }, { "no service change necessary (older systemd)", safcm.MsgSyncReq{ Services: []string{ "service-one", "service-two", }, }, [][]byte{ []byte(`ActiveState=active UnitFileState=enabled LoadError= "" ActiveState=active UnitFileState=enabled LoadError= "" `), }, [][]byte{nil}, []error{nil}, []*exec.Cmd{&exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "show", "--property=ActiveState,UnitFileState,LoadError", "--", "service-one", "service-two", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }}, []string{ "4: sync remote: services: checking service-one service-two (systemd detected)", `4: sync remote: services: running "/bin/systemctl" "show" "--property=ActiveState,UnitFileState,LoadError" "--" "service-one" "service-two"`, `5: sync remote: services: command stdout: ActiveState=active UnitFileState=enabled LoadError= "" ActiveState=active UnitFileState=enabled LoadError= "" `, }, safcm.MsgSyncResp{}, nil, }, { "invalid service", safcm.MsgSyncReq{ Services: []string{ "service-does-not-exist", "service-two", }, }, [][]byte{ []byte(`ActiveState=inactive UnitFileState= LoadError=org.freedesktop.systemd1.NoSuchUnit "Unit service-does-not-exist.service not found." ActiveState=active UnitFileState=enabled LoadError= `), }, [][]byte{nil}, []error{nil}, []*exec.Cmd{&exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "show", "--property=ActiveState,UnitFileState,LoadError", "--", "service-does-not-exist", "service-two", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }}, []string{ "4: sync remote: services: checking service-does-not-exist service-two (systemd detected)", `4: sync remote: services: running "/bin/systemctl" "show" "--property=ActiveState,UnitFileState,LoadError" "--" "service-does-not-exist" "service-two"`, `5: sync remote: services: command stdout: ActiveState=inactive UnitFileState= LoadError=org.freedesktop.systemd1.NoSuchUnit "Unit service-does-not-exist.service not found." ActiveState=active UnitFileState=enabled LoadError= `, }, safcm.MsgSyncResp{}, fmt.Errorf("systemd unit \"service-does-not-exist\" not found"), }, { "start/enable service", safcm.MsgSyncReq{ Services: []string{ "service-one", "service-two", "service-three", }, }, [][]byte{ []byte(`ActiveState=inactive UnitFileState=enabled LoadError= ActiveState=active UnitFileState=disabled LoadError= ActiveState=failed UnitFileState=disabled LoadError= `), nil, nil, nil, }, [][]byte{ nil, nil, nil, []byte("fake stderr"), }, []error{nil, nil, nil, nil}, []*exec.Cmd{&exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "show", "--property=ActiveState,UnitFileState,LoadError", "--", "service-one", "service-two", "service-three", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }, &exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "daemon-reload", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }, &exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "start", "--", "service-one", "service-three", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }, &exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "enable", "--", "service-two", "service-three", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }}, []string{ "4: sync remote: services: checking service-one service-two service-three (systemd detected)", `4: sync remote: services: running "/bin/systemctl" "show" "--property=ActiveState,UnitFileState,LoadError" "--" "service-one" "service-two" "service-three"`, `5: sync remote: services: command stdout: ActiveState=inactive UnitFileState=enabled LoadError= ActiveState=active UnitFileState=disabled LoadError= ActiveState=failed UnitFileState=disabled LoadError= `, `4: sync remote: services: running "/bin/systemctl" "daemon-reload"`, "3: sync remote: services: starting service-one service-three", `4: sync remote: services: running "/bin/systemctl" "start" "--" "service-one" "service-three"`, "3: sync remote: services: enabling service-two service-three", `4: sync remote: services: running "/bin/systemctl" "enable" "--" "service-two" "service-three"`, "5: sync remote: services: command stderr:\nfake stderr", }, safcm.MsgSyncResp{ ServiceChanges: []safcm.ServiceChange{ { Name: "service-one", Started: true, }, { Name: "service-two", Enabled: true, }, { Name: "service-three", Started: true, Enabled: true, }, }, }, nil, }, { "start/enable service (dry-run)", safcm.MsgSyncReq{ DryRun: true, Services: []string{ "service-one", "service-two", "service-three", }, }, [][]byte{ []byte(`ActiveState=inactive UnitFileState=enabled LoadError= ActiveState=active UnitFileState=disabled LoadError= ActiveState=failed UnitFileState=disabled LoadError= `), }, [][]byte{nil}, []error{nil}, []*exec.Cmd{&exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "show", "--property=ActiveState,UnitFileState,LoadError", "--", "service-one", "service-two", "service-three", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }}, []string{ "4: sync remote: services: checking service-one service-two service-three (systemd detected)", `4: sync remote: services: running "/bin/systemctl" "show" "--property=ActiveState,UnitFileState,LoadError" "--" "service-one" "service-two" "service-three"`, `5: sync remote: services: command stdout: ActiveState=inactive UnitFileState=enabled LoadError= ActiveState=active UnitFileState=disabled LoadError= ActiveState=failed UnitFileState=disabled LoadError= `, }, safcm.MsgSyncResp{ ServiceChanges: []safcm.ServiceChange{ { Name: "service-one", Started: true, }, { Name: "service-two", Enabled: true, }, { Name: "service-three", Started: true, Enabled: true, }, }, }, nil, }, { "start/enable service (error)", safcm.MsgSyncReq{ Services: []string{ "service-one", "service-two", "service-three", }, }, [][]byte{ []byte(`ActiveState=inactive UnitFileState=enabled LoadError= ActiveState=active UnitFileState=disabled LoadError= ActiveState=failed UnitFileState=disabled LoadError= `), nil, nil, }, [][]byte{ nil, nil, []byte("fake stderr"), }, []error{ nil, nil, fmt.Errorf("fake error"), }, []*exec.Cmd{&exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "show", "--property=ActiveState,UnitFileState,LoadError", "--", "service-one", "service-two", "service-three", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }, &exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "daemon-reload", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }, &exec.Cmd{ Path: "/bin/systemctl", Args: []string{ "/bin/systemctl", "start", "--", "service-one", "service-three", }, Stdout: &bytes.Buffer{}, Stderr: &bytes.Buffer{}, }}, []string{ "4: sync remote: services: checking service-one service-two service-three (systemd detected)", `4: sync remote: services: running "/bin/systemctl" "show" "--property=ActiveState,UnitFileState,LoadError" "--" "service-one" "service-two" "service-three"`, `5: sync remote: services: command stdout: ActiveState=inactive UnitFileState=enabled LoadError= ActiveState=active UnitFileState=disabled LoadError= ActiveState=failed UnitFileState=disabled LoadError= `, `4: sync remote: services: running "/bin/systemctl" "daemon-reload"`, "3: sync remote: services: starting service-one service-three", `4: sync remote: services: running "/bin/systemctl" "start" "--" "service-one" "service-three"`, "5: sync remote: services: command stderr:\nfake stderr", }, safcm.MsgSyncResp{ ServiceChanges: []safcm.ServiceChange{ { Name: "service-one", Started: true, }, { Name: "service-two", Enabled: true, }, { Name: "service-three", Started: true, Enabled: true, }, }, }, fmt.Errorf(`"/bin/systemctl" "start" "--" "service-one" "service-three" failed: fake error; stdout: "", stderr: "fake stderr"`), }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { s, res := prepareSync(tc.req, &testRunner{ t: t, expCmds: tc.expCmds, resStdout: tc.stdout, resStderr: tc.stderr, resError: tc.errors, }) err := s.syncServicesSystemd() testutil.AssertErrorEqual(t, "err", err, tc.expErr) dbg := res.Wait() testutil.AssertEqual(t, "resp", s.resp, tc.expResp) testutil.AssertEqual(t, "dbg", dbg, tc.expDbg) }) } }