package main import ( "strings" "testing" ) const localectlLaptop = `System Locale: LANG=en_US.UTF-8 LANGUAGE=en_US VC Keymap: (unset) X11 Layout: (unset) ` const localectlAnchor = `System Locale: LANG=en_US.UTF-8 LC_TIME=nl_BE.UTF-8 VC Keymap: de-latin1-nodeadkeys X11 Layout: de X11 Model: pc105 ` func TestLocalectlIsReadWithItsContinuationLinesAndUnsetAsEmpty(t *testing.T) { l := ParseLocalectl(localectlLaptop) if l.Locale["LANG"] != "en_US.UTF-8" || l.Locale["LANGUAGE"] != "en_US" || l.Keymap != "" || len(l.X11) != 0 { t.Fatalf("%+v", l) } a := ParseLocalectl(localectlAnchor) if a.Locale["LC_TIME"] != "nl_BE.UTF-8" || a.Keymap != "de-latin1-nodeadkeys" || a.X11["layout"] != "de" || a.X11["model"] != "pc105" { t.Fatalf("%+v", a) } } func TestALocaleIsGeneratedWhateverTheCodesetsSpelling(t *testing.T) { have := []string{"C", "C.utf8", "POSIX", "en_US.utf8", "nl_BE.utf8@euro"} if !generated(have, "en_US.UTF-8") || generated(have, "de_DE.UTF-8") || generated(have, "") || !generated(have, "nl_BE.UTF-8@euro") { t.Fatal("generated") } } func getMachine(zone, keymapStatus string) *Machine { return machine(byLine(map[string]Ran{ "localectl status": {Stdout: keymapStatus}, "timedatectl show": {Stdout: "Timezone=" + zone + "\nLocalRTC=no\nCanNTP=yes\nNTP=yes\nNTPSynchronized=yes\n"}, "locale -a": {Stdout: "C\nC.utf8\nPOSIX\nen_US.utf8\n"}, }, nil), 1000) } func TestGetSaysWhetherEachOfTheThreeIsAsDeclared(t *testing.T) { s, err := getMachine("Europe/Berlin", localectlAnchor).Get() if err != nil { t.Fatal(err) } if s.TimeZone != "Europe/Berlin" || !s.NTP || !s.NTPSynced || s.LocalRTC || s.Keymap != "de-latin1-nodeadkeys" || !s.LangGenerated { t.Fatalf("%+v", s) } if !s.AsDeclared["locale"] || s.AsDeclared["time_zone"] || s.AsDeclared["keymap"] { t.Fatalf("as declared: %v", s.AsDeclared) } s, _ = getMachine("Europe/Brussels", strings.Replace(localectlLaptop, "VC Keymap: (unset)", "VC Keymap: us", 1)).Get() if !s.AsDeclared["locale"] || !s.AsDeclared["time_zone"] || !s.AsDeclared["keymap"] { t.Fatalf("as declared: %v", s.AsDeclared) } } func zoneMachine(zones *[]string, calls *[]call) *Machine { current := "Europe/Berlin" return machine(fake(func(c call) Ran { switch c.String() { case "timedatectl show --property=Timezone --value": return Ran{Stdout: current + "\n"} case "timedatectl list-timezones": return Ran{Stdout: strings.Join(*zones, "\n") + "\n"} case "sudo -n timedatectl set-timezone Europe/Brussels", "timedatectl set-timezone Europe/Brussels": current = "Europe/Brussels" return Ran{} case "sudo -n timedatectl set-timezone Europe/Paris": current = "Europe/Paris" return Ran{} } return Ran{Status: 99, Stderr: "unexpected: " + c.String()} }, calls), 1000) } func TestSettingTheZoneEscalatesIsReadBackAndRefusesAnUnknownZone(t *testing.T) { zones := []string{"Europe/Berlin", "Europe/Brussels", "Europe/Paris"} var calls []call m := zoneMachine(&zones, &calls) z, err := m.TimeZone("Europe/Brussels", "") if err != nil || !z.Changed || z.Before != "Europe/Berlin" || z.TimeZone != "Europe/Brussels" || z.Note != "" { t.Fatalf("%+v %v", z, err) } if _, err := m.TimeZone("Mars/Olympus", ""); err == nil || !strings.Contains(err.Error(), "not a time zone this machine knows") { t.Fatalf("an unknown zone: %v", err) } z, err = m.TimeZone("Europe/Paris", "") if err != nil || !strings.Contains(z.Note, "declares Europe/Brussels") { t.Fatalf("another zone than the declared one is said: %+v %v", z, err) } z, _ = m.TimeZone("", "bru") if z.Count != 1 || z.Zones[0] != "Europe/Brussels" || z.Changed { t.Fatalf("match: %+v", z) } } func TestTheStepSetsTheZoneOnlyWhenItDiffersAsRoot(t *testing.T) { zones := []string{"Europe/Brussels"} var calls []call m := zoneMachine(&zones, &calls) m.UID = 0 m.ReadFile = func(p string) ([]byte, error) { if p == "/usr/share/zoneinfo/Europe/Brussels" { return []byte("TZif"), nil } return nil, errNoFile } said, err := m.SetTimeZoneStep("Europe/Brussels") if err != nil || said != "the time zone was Europe/Berlin and is now Europe/Brussels" { t.Fatalf("%q %v", said, err) } for _, c := range calls { if c.name == "sudo" { t.Fatal("the step runs as root and does not go through sudo") } } calls = nil said, err = m.SetTimeZoneStep("Europe/Brussels") if err != nil || !strings.Contains(said, "already") { t.Fatalf("%q %v", said, err) } for _, c := range calls { if strings.Contains(c.String(), "set-timezone") { t.Fatal("a machine already in the zone was set again") } } if _, err := m.SetTimeZoneStep("Nowhere/Here"); err == nil { t.Fatal("a zone not in the database was set") } if _, err := m.SetTimeZoneStep("../etc"); err == nil { t.Fatal("a path was taken for a zone") } } func TestLocalesAreListedAndAvailableOnesOnlyWhenNarrowed(t *testing.T) { files := map[string]string{ "/etc/locale.conf": "LANG=en_US.UTF-8\n", "/etc/locale.gen": "# en_US.UTF-8 UTF-8\nen_US.UTF-8 UTF-8 \n#nl_BE.UTF-8 UTF-8\n", "/usr/share/i18n/SUPPORTED": "en_US.UTF-8 UTF-8\nen_US ISO-8859-1\nnl_BE.UTF-8 UTF-8\n", } m := machine(byLine(map[string]Ran{"locale -a": {Stdout: "C\nen_US.utf8\n"}}, nil), 1000) m.ReadFile = func(p string) ([]byte, error) { if s, ok := files[p]; ok { return []byte(s), nil } return nil, errNoFile } l, err := m.Locales("") if err != nil { t.Fatal(err) } if l.Lang != "en_US.UTF-8" || !l.LangGenerated || len(l.Enabled) != 1 || l.AvailableCount != 3 || l.Available != nil { t.Fatalf("%+v", l) } l, _ = m.Locales("nl_") if len(l.Available) != 1 || l.Available[0] != "nl_BE.UTF-8 UTF-8" { t.Fatalf("%+v", l.Available) } } func TestKeymapsAreNarrowedAndTheCurrentOneSaid(t *testing.T) { m := machine(byLine(map[string]Ran{ "localectl status": {Stdout: localectlAnchor}, "localectl list-keymaps --no-pager": {Stdout: "be-latin1\nde-latin1\nde-latin1-nodeadkeys\nus\n"}, }, nil), 1000) k, err := m.Keymaps("de") if err != nil || k.Current != "de-latin1-nodeadkeys" || k.Count != 2 { t.Fatalf("%+v %v", k, err) } }