A fresh profile has never checked for updates, so Betterbird checked at the test's start, found a newer build published and opened its About window on the operator's screen instead of leaving the reminder to show. The test's own preferences now also come last in its user.js, so nothing declared or carried overrides them.
264 lines
11 KiB
Go
264 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const profileDir = testHome + "/.thunderbird/60016jsp.default-default"
|
|
|
|
// links stands in for the kernel's fd links: path → target.
|
|
func links(t *testing.T, m map[string]string) {
|
|
t.Helper()
|
|
old := readLink
|
|
readLink = func(p string) (string, error) {
|
|
for k, v := range m {
|
|
if strings.HasSuffix(p, k) {
|
|
return v, nil
|
|
}
|
|
}
|
|
return "", os.ErrNotExist
|
|
}
|
|
t.Cleanup(func() { readLink = old })
|
|
}
|
|
|
|
// betterbird is a running Betterbird with one child, its profile and its prefs.
|
|
func (f *fake) betterbird() {
|
|
f.proc(4000, 1000, "betterbird-bin", []string{"betterbird"}, "session-4.scope", "DISPLAY=:1")
|
|
f.proc(4001, 1000, "betterbird-bin", []string{"/opt/betterbird/betterbird-bin", "-contentproc", "-isForBrowser"}, "session-4.scope")
|
|
f.write("/proc/4000/fd/40", "")
|
|
f.write("/proc/4000/fd/41", "")
|
|
f.write("/proc/4000/fd/1", "")
|
|
f.write(testHome+"/.thunderbird/profiles.ini", "[Profile0]\nName=default-default\nIsRelative=1\nPath=60016jsp.default-default\n\n"+
|
|
"[Profile1]\nName=default\nIsRelative=1\nPath=au2i98w4.default\nDefault=1\n\n[InstallF72D9E66FA3C07ED]\nDefault=60016jsp.default-default\nLocked=1\n")
|
|
f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+
|
|
`user_pref("browser.display.use_system_colors", true);`+"\n"+
|
|
`user_pref("extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org");`+"\n"+
|
|
`user_pref("calendar.alarms.showmissed", true);`+"\n"+
|
|
`user_pref("mail.identity.id1.useremail", "someone@example.org");`+"\n"+
|
|
`user_pref("extensions.owl.token", "abc");`+"\n"+
|
|
`user_pref("signon.rememberSignons", true);`+"\n")
|
|
f.write(profileDir+"/extensions.json", `{"addons":[{"id":"owl@beonex.com","active":true,"location":"app-profile"},{"id":"x@mozilla.org","active":true,"location":"app-builtin"}]}`)
|
|
}
|
|
|
|
func (f *fake) declare(prefs string) {
|
|
f.write(testHome+"/.config/betterbird/user.js", mark+", novox/hq issue 345). Owned by the mesh.\n//\n"+prefs+"\n")
|
|
}
|
|
|
|
func TestTheProfileIsTheOneTheRunningBetterbirdHoldsElseTheInstallationsDefault(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
links(t, map[string]string{"/proc/4000/fd/40": profileDir + "/prefs.js", "/proc/4000/fd/41": profileDir + "/places.sqlite"})
|
|
p, err := f.profile()
|
|
if err != nil || p.Dir != "~/.thunderbird/60016jsp.default-default" || !strings.Contains(p.FoundBy, "pid 4000, 2 files") {
|
|
t.Fatalf("%+v %v", p, err)
|
|
}
|
|
links(t, map[string]string{})
|
|
p, err = f.profile()
|
|
if err != nil || p.Dir != "~/.thunderbird/60016jsp.default-default" || !strings.Contains(p.FoundBy, "InstallF72D9E66FA3C07ED") {
|
|
t.Fatalf("the installation's default, not Profile1's Default=1: %+v %v", p, err)
|
|
}
|
|
}
|
|
|
|
func TestPrefsAnswerValuesButNeverACredentialOrAnAccount(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
links(t, nil)
|
|
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("browser.display.use_system_colors", false);`+"\n")
|
|
got, err := f.Prefs([]string{"browser.display.", "mail.identity.*", "extensions.owl.token", "signon.rememberSignons", "gfx.webrender.software"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ps := got["prefs"].([]Pref)
|
|
by := map[string]Pref{}
|
|
for _, p := range ps {
|
|
by[p.Name] = p
|
|
}
|
|
if p := by["browser.display.use_system_colors"]; p.Value != "false" || !strings.HasPrefix(p.From, "user.js") {
|
|
t.Fatalf("user.js wins: %+v", p)
|
|
}
|
|
for _, n := range []string{"mail.identity.id1.useremail", "extensions.owl.token", "signon.rememberSignons"} {
|
|
if p := by[n]; p.Value != "" || p.Withheld == "" {
|
|
t.Errorf("%s: %+v", n, p)
|
|
}
|
|
}
|
|
if p := by["gfx.webrender.software"]; p.Value != "" || !strings.Contains(p.From, "default") {
|
|
t.Fatalf("%+v", p)
|
|
}
|
|
if _, err := f.Prefs(nil); err == nil {
|
|
t.Fatal("names are required")
|
|
}
|
|
}
|
|
|
|
func TestTheModulesUserJSIsPlacedOnlyWhereNoOperatorsFileIs(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
links(t, nil)
|
|
if _, err := f.userJS(true); err == nil || !strings.Contains(err.Error(), "not pushed") {
|
|
t.Fatalf("nothing declared: %v", err)
|
|
}
|
|
f.declare(`user_pref("calendar.alarms.showmissed", true); user_pref("ui.key.x", "a;b");`)
|
|
u, err := f.userJS(true)
|
|
if err != nil || !u.Applied || u.Statements != 2 {
|
|
t.Fatalf("%+v %v", u, err)
|
|
}
|
|
placed, _ := os.ReadFile(filepath.Join(f.Root, profileDir, "user.js"))
|
|
declared, _ := os.ReadFile(filepath.Join(f.Root, testHome, ".config/betterbird/user.js"))
|
|
if string(placed) != string(declared) {
|
|
t.Fatal("placed whole, as declared")
|
|
}
|
|
if !strings.HasPrefix(u.Effective, "yes") {
|
|
t.Fatalf("the fake Betterbird started after the file's time: %q", u.Effective)
|
|
}
|
|
later := time.Unix(1_900_000_000, 0)
|
|
os.Chtimes(filepath.Join(f.Root, profileDir, "user.js"), later, later)
|
|
if u, _ := f.userJS(false); !strings.HasPrefix(u.Effective, "not yet") {
|
|
t.Fatalf("placed after Betterbird started: %q", u.Effective)
|
|
}
|
|
f.write(profileDir+"/user.js", `user_pref("a.b", 1);`+"\n")
|
|
if _, err := f.userJS(true); err == nil || !strings.Contains(err.Error(), "not the module's") {
|
|
t.Fatalf("an operator's user.js is never overwritten: %v", err)
|
|
}
|
|
if b, _ := os.ReadFile(filepath.Join(f.Root, profileDir, "user.js")); string(b) != `user_pref("a.b", 1);`+"\n" {
|
|
t.Fatal("and it is left as it was")
|
|
}
|
|
}
|
|
|
|
func TestTheUserJSHoldsOnlyPreferencesAndNoCredential(t *testing.T) {
|
|
for text, ok := range map[string]bool{
|
|
mark + ")\n// none\n": true,
|
|
mark + ")\n" + `user_pref("a.b", true); user_pref("c", -3); user_pref("d", "x\"y;z");`: true,
|
|
mark + ")\n" + `pref("a.b", true);`: false,
|
|
mark + ")\n" + `user_pref("a.b", true); Services.foo();`: false,
|
|
mark + ")\n" + `user_pref("mail.smtpserver.s1.password", "x");`: false,
|
|
`user_pref("a.b", true);`: false,
|
|
} {
|
|
if err := validUserJS(text); (err == nil) != ok {
|
|
t.Errorf("%q: %v", text, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStatusSaysWhatRunsTheProfileTheAddOnsAndTheDisplayPrefs(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
links(t, map[string]string{"/proc/4000/fd/1": "pipe:[36802]"})
|
|
f.write("/opt/betterbird/application.ini", "[App]\nVendor=Betterbird Project\nName=Betterbird\nVersion=153.1.0\nBuildID=20260820183427\n")
|
|
f.answer = func(name string, args []string) Output {
|
|
if name == "pacman" && args[0] == "-Q" {
|
|
return Output{Stdout: "betterbird-bin 153.1.0esr-1\n"}
|
|
}
|
|
if name == "pacman" && args[0] == "-Qmq" {
|
|
return Output{Stdout: "betterbird-bin\n"}
|
|
}
|
|
return Output{}
|
|
}
|
|
s, err := f.Status()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(s.Running) != 1 || s.Running[0].PID != 4000 || s.Children != 1 || s.Installed != "153.1.0esr-1" || !s.Foreign ||
|
|
s.Version != "153.1.0" || s.Profile == nil || len(s.AddOns) != 1 || s.AddOns[0].ID != "owl@beonex.com" {
|
|
t.Fatalf("%+v", s)
|
|
}
|
|
if !s.Output[0].Dead {
|
|
t.Fatalf("the session's dead pipe: %+v", s.Output)
|
|
}
|
|
found := false
|
|
for _, p := range s.Display {
|
|
if p.Name == "extensions.activeThemeID" && p.Value == `"thunderbird-compact-dark@mozilla.org"` {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("%+v", s.Display)
|
|
}
|
|
}
|
|
|
|
func TestCheckNamesWhatIsMissingAndWhatToDo(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
links(t, map[string]string{"/proc/4000/fd/1": "pipe:[1]", "/proc/4000/fd/2": "/dev/null"})
|
|
f.answer = func(name string, args []string) Output { return Output{Stdout: "betterbird-bin 153.1.0esr-1\n"} }
|
|
a, err := f.Check()
|
|
if err != nil || a.OK {
|
|
t.Fatalf("%+v %v", a, err)
|
|
}
|
|
all := ""
|
|
for _, x := range a.Findings {
|
|
all += x.What + " | " + x.Do + "\n"
|
|
}
|
|
if !strings.Contains(all, "not on this machine") || !strings.Contains(all, "pipe nobody reads") {
|
|
t.Fatal(all)
|
|
}
|
|
}
|
|
|
|
func TestRestartEndsItPlacesTheUserJSAndStartsItUnderTheServiceManager(t *testing.T) {
|
|
f := newFake(t)
|
|
f.desktopSession()
|
|
f.betterbird()
|
|
links(t, nil)
|
|
f.declare("// none")
|
|
f.onStart = func(argv []string) {
|
|
f.proc(5000, 1000, "betterbird-bin", []string{"betterbird"}, "mesh-betterbird.service")
|
|
}
|
|
a, err := f.Restart(false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(a.Ended) != 1 || a.Ended[0] != 4000 || len(a.Running) != 1 || a.Running[0].PID != 5000 || !strings.HasPrefix(a.UserJS, "placed") {
|
|
t.Fatalf("%+v", a)
|
|
}
|
|
if !f.called("systemd-run --user --collect --quiet --unit=mesh-betterbird") {
|
|
t.Fatalf("%v", f.calls)
|
|
}
|
|
}
|
|
|
|
func TestTheReminderTestTouchesNothingOfTheOperatorsProfile(t *testing.T) {
|
|
f := newFake(t)
|
|
f.desktopSession()
|
|
f.betterbird()
|
|
links(t, nil)
|
|
f.declare(`user_pref("calendar.alarms.show", true);`)
|
|
before, _ := os.ReadFile(filepath.Join(f.Root, profileDir, "prefs.js"))
|
|
a, err := f.ReminderTest(40*time.Second, []string{"extensions.activeThemeID", "mail.identity.id1.useremail", "nope.nope"}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Join(a.Carried, ",") != "extensions.activeThemeID" || len(a.Withheld) != 2 {
|
|
t.Fatalf("%+v", a)
|
|
}
|
|
dir := filepath.Join(f.Root, "/run/user/1000", testUnit)
|
|
user, _ := os.ReadFile(filepath.Join(dir, "profile", "user.js"))
|
|
cal, _ := os.ReadFile(filepath.Join(dir, "test.ics"))
|
|
if !strings.Contains(string(user), `"calendar.alarms.show", true`) || !strings.Contains(string(user), `"extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org"`) ||
|
|
!strings.Contains(string(user), "file:///run/user/1000/"+testUnit+"/test.ics") || strings.Contains(string(user), "someone@") {
|
|
t.Fatalf("%s", user)
|
|
}
|
|
// Betterbird's update check stays off, so no About window opens on the operator's screen, and the
|
|
// test's own preferences come last, so nothing carried or declared overrides them.
|
|
if !strings.Contains(string(user), `"betterbird.update.checkIntervalDays", 0`) ||
|
|
strings.Index(string(user), "extensions.activeThemeID") > strings.Index(string(user), "calendar.registry.meshtest.uri") ||
|
|
strings.Index(string(user), `"calendar.alarms.show"`) > strings.Index(string(user), "calendar.registry.meshtest.uri") {
|
|
t.Fatalf("%s", user)
|
|
}
|
|
// The event starts 10 minutes after the reminder, which is due 40 s from now.
|
|
if !strings.Contains(string(cal), "DTSTART:20270115T081040Z") || !strings.Contains(string(cal), "TRIGGER:-PT10M") ||
|
|
a.Due != "2027-01-15T08:00:40Z" {
|
|
t.Fatalf("%s %s", cal, a.Due)
|
|
}
|
|
if !f.called("systemd-run --user --collect --quiet --unit=" + testUnit + " -p RuntimeMaxSec=600 -p ExecStopPost=/usr/bin/rm -rf /run/user/1000/" + testUnit + " --setenv=MOZ_NO_REMOTE=1") {
|
|
t.Fatalf("%v", f.calls)
|
|
}
|
|
after, _ := os.ReadFile(filepath.Join(f.Root, profileDir, "prefs.js"))
|
|
if string(before) != string(after) || exists(filepath.Join(f.Root, profileDir, "user.js")) {
|
|
t.Fatal("the operator's profile is untouched")
|
|
}
|
|
if _, err := f.ReminderTest(0, nil, true); err != nil || exists(dir) {
|
|
t.Fatalf("stop removes the test profile: %v", err)
|
|
}
|
|
}
|