206 lines
5.6 KiB
Go
206 lines
5.6 KiB
Go
package peer
|
|
|
|
import (
|
|
"net/netip"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// writeTempHosts creates a temp hosts file with the given content and returns
|
|
// its path.
|
|
func writeTempHosts(t *testing.T, content string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "hosts")
|
|
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// readManagedSection returns the lines between the begin/end markers for the
|
|
// given localDomain, plus everything outside the section ("outside").
|
|
func readManagedSection(t *testing.T, path, localDomain string) (inside, outside []string) {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
begin, end := hostMarkers(localDomain)
|
|
|
|
inSection := false
|
|
for _, line := range strings.Split(string(raw), "\n") {
|
|
switch {
|
|
case strings.HasPrefix(line, begin):
|
|
inSection = true
|
|
case strings.HasPrefix(line, end):
|
|
inSection = false
|
|
case inSection:
|
|
if f := strings.Join(strings.Fields(line), " "); f != "" {
|
|
inside = append(inside, f)
|
|
}
|
|
default:
|
|
if f := strings.Join(strings.Fields(line), " "); f != "" {
|
|
outside = append(outside, f)
|
|
}
|
|
}
|
|
}
|
|
return inside, outside
|
|
}
|
|
|
|
func peer(name string) *Peer {
|
|
return &Peer{Name: name}
|
|
}
|
|
|
|
func TestUpdateHosts_AddsSection(t *testing.T) {
|
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
|
|
|
peers := map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
|
netip.MustParseAddr("10.11.12.10"): peer("laptop"),
|
|
}
|
|
|
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
inside, outside := readManagedSection(t, path, "mynet.local")
|
|
|
|
sort.Strings(inside)
|
|
want := []string{
|
|
"10.11.12.1 hub.mynet.local",
|
|
"10.11.12.10 laptop.mynet.local",
|
|
}
|
|
if strings.Join(inside, "\n") != strings.Join(want, "\n") {
|
|
t.Errorf("managed section = %v, want %v", inside, want)
|
|
}
|
|
|
|
if !contains(outside, "127.0.0.1 localhost") {
|
|
t.Errorf("original content lost; outside = %v", outside)
|
|
}
|
|
}
|
|
|
|
func TestUpdateHosts_ReplacesExistingSection(t *testing.T) {
|
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
|
|
|
// First write.
|
|
first := map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
|
}
|
|
if err := updateHosts(path, "mynet.local", first); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Second write with a different set of peers.
|
|
second := map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.11.12.20"): peer("phone"),
|
|
}
|
|
if err := updateHosts(path, "mynet.local", second); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
inside, outside := readManagedSection(t, path, "mynet.local")
|
|
|
|
if len(inside) != 1 || inside[0] != "10.11.12.20 phone.mynet.local" {
|
|
t.Errorf("section not replaced; inside = %v", inside)
|
|
}
|
|
if contains(inside, "10.11.12.1 hub.mynet.local") {
|
|
t.Errorf("stale entry remained; inside = %v", inside)
|
|
}
|
|
if !contains(outside, "127.0.0.1 localhost") {
|
|
t.Errorf("original content lost; outside = %v", outside)
|
|
}
|
|
}
|
|
|
|
func TestUpdateHosts_SkipsEmptyNames(t *testing.T) {
|
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
|
|
|
peers := map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
|
netip.MustParseAddr("10.11.12.99"): peer(""), // no name
|
|
}
|
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
inside, _ := readManagedSection(t, path, "mynet.local")
|
|
if len(inside) != 1 || inside[0] != "10.11.12.1 hub.mynet.local" {
|
|
t.Errorf("expected only named peer; inside = %v", inside)
|
|
}
|
|
}
|
|
|
|
func TestUpdateHosts_Idempotent(t *testing.T) {
|
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
|
|
|
peers := map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
|
}
|
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(first) != string(second) {
|
|
t.Errorf("repeated update changed file:\nfirst:\n%s\nsecond:\n%s", first, second)
|
|
}
|
|
}
|
|
|
|
// TestUpdateHosts_PrefixDomainsCoexist guards finding 4.4: two domains where
|
|
// one label is a prefix of the other ("net" vs "net2") must each manage their
|
|
// own section without clobbering the other's, even sharing one hosts file.
|
|
func TestUpdateHosts_PrefixDomainsCoexist(t *testing.T) {
|
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
|
|
|
if err := updateHosts(path, "net2.local", map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.0.2.1"): peer("a"),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := updateHosts(path, "net.local", map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.0.1.1"): peer("b"),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Both sections coexist after writing the prefix domain.
|
|
if in, _ := readManagedSection(t, path, "net2.local"); len(in) != 1 || in[0] != "10.0.2.1 a.net2.local" {
|
|
t.Errorf("net2 section clobbered: %v", in)
|
|
}
|
|
if in, _ := readManagedSection(t, path, "net.local"); len(in) != 1 || in[0] != "10.0.1.1 b.net.local" {
|
|
t.Errorf("net section wrong: %v", in)
|
|
}
|
|
|
|
// Re-updating net2 must not disturb the net section.
|
|
if err := updateHosts(path, "net2.local", map[netip.Addr]*Peer{
|
|
netip.MustParseAddr("10.0.2.2"): peer("c"),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if in, _ := readManagedSection(t, path, "net.local"); len(in) != 1 || in[0] != "10.0.1.1 b.net.local" {
|
|
t.Errorf("net section disturbed by net2 update: %v", in)
|
|
}
|
|
if in, _ := readManagedSection(t, path, "net2.local"); len(in) != 1 || in[0] != "10.0.2.2 c.net2.local" {
|
|
t.Errorf("net2 section not updated: %v", in)
|
|
}
|
|
}
|
|
|
|
func contains(ss []string, s string) bool {
|
|
for _, x := range ss {
|
|
if x == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|