One machine ran another time zone and a German console keymap with no record why. The module writes /etc/locale.conf and /etc/vconsole.conf whole and sets the zone through a run-once step of its own Go binary (timedatectl, read back): /etc/localtime is a link the mesh may not write (hq ADR 0012) and a module may not declare an action (ADR 0005). Tools: localization_get, _time_zone, _locales, _keymaps (to-be 42 Phase 1).
178 lines
6.0 KiB
Go
178 lines
6.0 KiB
Go
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)
|
|
}
|
|
}
|