// 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 main import ( "bytes" "fmt" "log" "os" "testing" "ruderich.org/simon/safcm" "ruderich.org/simon/safcm/cmd/safcm/config" "ruderich.org/simon/safcm/rpc" "ruderich.org/simon/safcm/testutil" ) func TestHostsToSync(t *testing.T) { cwd, err := os.Getwd() if err != nil { t.Fatal(err) } defer os.Chdir(cwd) err = os.Chdir("testdata/project") if err != nil { t.Fatal(err) } _, allHosts, allGroups, err := LoadBaseFiles() if err != nil { t.Fatal(err) } tests := []struct { name string names []string exp []*config.Host expErr error }{ { "empty names", nil, nil, nil, }, { "no match", []string{"unknown-host/group"}, nil, fmt.Errorf("hosts/groups not found: \"unknown-host/group\""), }, { "host: single name", []string{"host2"}, []*config.Host{ allHosts.Map["host2"], }, nil, }, { "host: multiple names", []string{"host2", "host1.example.org"}, []*config.Host{ allHosts.Map["host1.example.org"], allHosts.Map["host2"], }, nil, }, { "host: multiple identical names", []string{"host2", "host2"}, []*config.Host{ allHosts.Map["host2"], }, nil, }, { "host: multiple names, including unknown", []string{"host2", "unknown-host"}, nil, fmt.Errorf("hosts/groups not found: \"unknown-host\""), }, { "host: multiple names, including unknowns", []string{"host2", "unknown-host", "unknown-host-2"}, nil, fmt.Errorf("hosts/groups not found: \"unknown-host\" \"unknown-host-2\""), }, { "group: single name", []string{"group"}, []*config.Host{ allHosts.Map["host1.example.org"], }, nil, }, { "group: multiple names", []string{"group", "group2"}, []*config.Host{ allHosts.Map["host1.example.org"], allHosts.Map["host2"], }, nil, }, { "group: multiple identical names", []string{"group", "group2", "group"}, []*config.Host{ allHosts.Map["host1.example.org"], allHosts.Map["host2"], }, nil, }, { "group: multiple names, including unknown", []string{"group", "group2", "unknown-group"}, nil, fmt.Errorf("hosts/groups not found: \"unknown-group\""), }, { "group: \"all\"", []string{"all"}, []*config.Host{ allHosts.Map["host1.example.org"], allHosts.Map["host2"], allHosts.Map["host3.example.net"], }, nil, }, { "\"all\" and name", []string{"all", "group2"}, []*config.Host{ allHosts.Map["host1.example.org"], allHosts.Map["host2"], allHosts.Map["host3.example.net"], }, nil, }, { "\"all\" and names", []string{"all", "group2", "host2"}, []*config.Host{ allHosts.Map["host1.example.org"], allHosts.Map["host2"], allHosts.Map["host3.example.net"], }, nil, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { res, err := hostsToSync(tc.names, allHosts, allGroups) testutil.AssertEqual(t, "res", res, tc.exp) testutil.AssertErrorEqual(t, "err", err, tc.expErr) }) } } func TestLogEvent(t *testing.T) { // Restore default logger defer log.SetFlags(log.Flags()) defer log.SetOutput(os.Stderr) tests := []struct { name string event Event level safcm.LogLevel isTTY bool exp string expFailed bool }{ { "Error", Event{ Error: fmt.Errorf("fake error"), }, safcm.LogDebug3, false, "[error] [fake-host] fake error\n", true, }, { "Error (tty)", Event{ Error: fmt.Errorf("fake error"), }, safcm.LogDebug3, true, "[error] [\x1b[31mfake-host\x1b[0m] fake error\n", true, }, { "Error: escape", Event{ Error: fmt.Errorf("\x00"), }, safcm.LogDebug3, false, "[error] [fake-host] \\x00\n", true, }, { "Error: escape (tty)", Event{ Error: fmt.Errorf("\x00"), }, safcm.LogDebug3, true, "[error] [\x1b[31mfake-host\x1b[0m] \x1b[35m\\x00\x1b[0m\n", true, }, { "Log: info", Event{ Log: Log{ Level: safcm.LogInfo, Text: "info log", }, }, safcm.LogDebug3, false, "[info] [fake-host] info log\n", false, }, { "Log: info (tty)", Event{ Log: Log{ Level: safcm.LogInfo, Text: "info log", }, }, safcm.LogDebug3, true, "[info] [fake-host] info log\n", false, }, { "Log: verbose", Event{ Log: Log{ Level: safcm.LogVerbose, Text: "verbose log", }, }, safcm.LogDebug3, false, "[verbose] [fake-host] verbose log\n", false, }, { "Log: debug", Event{ Log: Log{ Level: safcm.LogDebug, Text: "debug log", }, }, safcm.LogDebug3, false, "[debug] [fake-host] debug log\n", false, }, { "Log: debug2", Event{ Log: Log{ Level: safcm.LogDebug2, Text: "debug2 log", }, }, safcm.LogDebug3, false, "[debug2] [fake-host] debug2 log\n", false, }, { "Log: debug3", Event{ Log: Log{ Level: safcm.LogDebug3, Text: "debug3 log", }, }, safcm.LogDebug3, false, fmt.Sprintf("[INVALID=%d] [fake-host] debug3 log\n", safcm.LogDebug3), false, }, { "Log: debug3 (tty)", Event{ Log: Log{ Level: safcm.LogDebug3, Text: "debug3 log", }, }, safcm.LogDebug3, true, fmt.Sprintf("[INVALID=%d] [\x1b[31mfake-host\x1b[0m] debug3 log\n", safcm.LogDebug3), false, }, { "Log: escape", Event{ Log: Log{ Level: safcm.LogInfo, Text: "\x00", }, }, safcm.LogDebug3, false, "[info] [fake-host] \\x00\n", false, }, { "Log: escape (tty)", Event{ Log: Log{ Level: safcm.LogInfo, Text: "\x00", }, }, safcm.LogDebug3, true, "[info] [fake-host] \x1b[35m\\x00\x1b[0m\n", false, }, { "ConnEvent: stderr", Event{ ConnEvent: rpc.ConnEvent{ Type: rpc.ConnEventStderr, Data: "fake stderr", }, }, safcm.LogDebug3, false, "[stderr] [fake-host] fake stderr\n", false, }, { "ConnEvent: stderr (tty)", Event{ ConnEvent: rpc.ConnEvent{ Type: rpc.ConnEventStderr, Data: "fake stderr", }, }, safcm.LogDebug3, true, "[stderr] [fake-host] fake stderr\n", false, }, { "ConnEvent: debug", Event{ ConnEvent: rpc.ConnEvent{ Type: rpc.ConnEventDebug, Data: "conn debug", }, }, safcm.LogDebug3, false, "[debug3] [fake-host] conn debug\n", false, }, { "ConnEvent: upload", Event{ ConnEvent: rpc.ConnEvent{ Type: rpc.ConnEventUpload, }, }, safcm.LogDebug3, false, "[info] [fake-host] remote helper upload in progress\n", false, }, { "ConnEvent: upload (ignored)", Event{ ConnEvent: rpc.ConnEvent{ Type: rpc.ConnEventUpload, }, }, safcm.LogError, false, "", false, }, { "ConnEvent: invalid", Event{ ConnEvent: rpc.ConnEvent{ Type: 42, Data: "invalid", }, }, safcm.LogError, false, "[INVALID=42] [fake-host] invalid\n", false, }, { "ConnEvent: invalid (tty)", Event{ ConnEvent: rpc.ConnEvent{ Type: 42, Data: "invalid", }, }, safcm.LogError, true, "[INVALID=42] [\x1b[31mfake-host\x1b[0m] invalid\n", false, }, { "ConnEvent: escape", Event{ ConnEvent: rpc.ConnEvent{ Type: rpc.ConnEventStderr, Data: "\x00", }, }, safcm.LogDebug3, false, "[stderr] [fake-host] \\x00\n", false, }, { "ConnEvent: escape (tty)", Event{ ConnEvent: rpc.ConnEvent{ Type: rpc.ConnEventDebug, Data: "\x01", }, }, safcm.LogDebug3, true, "[debug3] [fake-host] \x1b[35m\\x01\x1b[0m\n", false, }, { "Escaped", Event{ Log: Log{ Level: safcm.LogInfo, Text: "\x00", }, Escaped: true, }, safcm.LogDebug3, false, "[info] [fake-host] \x00\n", false, }, { "Escaped (tty)", Event{ Log: Log{ Level: safcm.LogInfo, Text: "\x00", }, Escaped: true, }, safcm.LogDebug3, true, "[info] [fake-host] \x00\n", false, }, { "empty (invalid)", Event{}, safcm.LogDebug3, false, "[INVALID=0] [fake-host] \n", false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { tc.event.Host = &config.Host{ Name: "fake-host", } var buf bytes.Buffer log.SetFlags(0) log.SetOutput(&buf) var failed bool logEvent(tc.event, tc.level, tc.isTTY, &failed) testutil.AssertEqual(t, "log", buf.String(), tc.exp) testutil.AssertEqual(t, "failed", failed, tc.expFailed) }) } }