Source code

Revision control

Copy as Markdown

Other Tools

#!/bin/bash
# Unit tests for pure functions in install-firefox.sh.
# Run locally: ./mach shell-test browser/installer/linux/script/install-firefox-test-unit.sh
set -u
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
INSTALL_FIREFOX_SCRIPT="$SCRIPT_DIR/install-firefox.sh"
setUp() {
CHANNEL="release"
INSTALL_METHOD=""
LANG_OVERRIDE=""
VERBOSE=false
ARCH=""
DISTRO=""
DISTRO_VERSION=""
DETECTED_LOCALE=""
unset _OS_RELEASE 2>/dev/null || true
INSTALL_FIREFOX_SOURCED=1 . "$INSTALL_FIREFOX_SCRIPT"
# install-firefox.sh sets -eu; reset -e so shunit2 can handle non-zero exits.
set +e
}
tearDown() {
rm -f "${SHUNIT_TMPDIR}/calls.tmp" "${SHUNIT_TMPDIR}/os-release" 2>/dev/null || true
unset -f curl wget check_cmd dpkg apt_cache_show_stub uname rpm_pkg_exists_stub flatpak run_sudo retry \
sleep id sudo _flaky install_apt install_rpm install_flatpak install_tarball \
download_to_stdout check_url need_cmd find_l10n_package zypper dnf tar gpg snap mktemp \
_apt_l10n_check _rpm_l10n_check _rpm_l10n_stub apt_verify_gpg_fingerprint 2>/dev/null || true
rm -f "${SHUNIT_TMPDIR}/flag.tmp" 2>/dev/null || true
unset _OS_RELEASE 2>/dev/null || true
}
testParseArgsChannelRelease() {
parse_args --channel release
assertEquals "channel=release" "release" "$CHANNEL"
}
testParseArgsChannelBeta() {
parse_args --channel beta
assertEquals "channel=beta" "beta" "$CHANNEL"
}
testParseArgsChannelDevedition() {
parse_args --channel devedition
assertEquals "channel=devedition" "devedition" "$CHANNEL"
}
testParseArgsChannelNightly() {
parse_args --channel nightly
assertEquals "channel=nightly" "nightly" "$CHANNEL"
}
testParseArgsChannelInvalid() {
assertFalse "invalid channel" 'sh "$INSTALL_FIREFOX_SCRIPT" --channel bogus >/dev/null 2>&1'
}
testParseArgsInstallMethodApt() {
parse_args --install-method apt
assertEquals "method=apt" "apt" "$INSTALL_METHOD"
}
testParseArgsInstallMethodInvalid() {
assertFalse "invalid method" 'sh "$INSTALL_FIREFOX_SCRIPT" --install-method bogus >/dev/null 2>&1'
}
testParseArgsLang() {
parse_args --lang fr
assertEquals "--lang sets LANG_OVERRIDE" "fr" "$LANG_OVERRIDE"
}
testParseArgsHelp() {
assertTrue "--help exits 0" 'sh "$INSTALL_FIREFOX_SCRIPT" --help >/dev/null 2>&1'
}
testChannelToPackageRelease() {
CHANNEL=release
assertEquals "pkg release" "firefox" "$(channel_to_package)"
}
testChannelToPackageBeta() {
CHANNEL=beta
assertEquals "pkg beta" "firefox-beta" "$(channel_to_package)"
}
testChannelToPackageDevedition() {
CHANNEL=devedition
assertEquals "pkg devedition" "firefox-devedition" "$(channel_to_package)"
}
testChannelToPackageNightly() {
CHANNEL=nightly
assertEquals "pkg nightly" "firefox-nightly" "$(channel_to_package)"
}
testDetectLocaleOverrideFr() {
LANG_OVERRIDE="fr"
detect_locale
assertEquals "--lang fr" "fr" "$DETECTED_LOCALE"
}
testDetectLocaleOverrideJa() {
LANG_OVERRIDE="ja"
detect_locale
assertEquals "--lang ja" "ja" "$DETECTED_LOCALE"
}
testDetectLocaleFrFr() {
LANG_OVERRIDE=""
LANG=fr_FR.UTF-8 LC_ALL= LC_MESSAGES= detect_locale
assertEquals "fr_FR.UTF-8 -> fr-FR" "fr-FR" "$DETECTED_LOCALE"
}
testDetectLocaleC() {
LANG_OVERRIDE=""
LANG=C LC_ALL= LC_MESSAGES= detect_locale
assertEquals "C -> en-US" "en-US" "$DETECTED_LOCALE"
}
testDetectLocalePosix() {
LANG_OVERRIDE=""
LANG=POSIX LC_ALL= LC_MESSAGES= detect_locale
assertEquals "POSIX -> en-US" "en-US" "$DETECTED_LOCALE"
}
testDetectLocaleBareLang() {
LANG_OVERRIDE=""
LANG=de LC_ALL= LC_MESSAGES= detect_locale
assertEquals "bare lang de -> de" "de" "$DETECTED_LOCALE"
}
testDetectLocaleJaJp() {
LANG_OVERRIDE=""
LANG=ja_JP.UTF-8 LC_ALL= LC_MESSAGES= detect_locale
assertEquals "ja_JP.UTF-8 -> ja-JP" "ja-JP" "$DETECTED_LOCALE"
}
testCheckUrlCurl() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
curl() { echo "curl $*" >> "$_calls"; return 0; }
wget() { echo "wget $*" >> "$_calls"; return 1; }
export -f curl wget
check_url "https://example.com"
assertTrue "check_url uses curl when available" "grep -q curl '$_calls'"
}
testCheckUrlWget() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
wget() { echo "wget $*" >> "$_calls"; return 0; }
export -f wget
check_cmd() { [ "$1" = "wget" ]; }; export -f check_cmd
check_url "https://example.com"
assertTrue "check_url falls back to wget" "grep -q wget '$_calls'"
}
testDetectBestMethodApt() {
check_cmd() { [ "$1" = "apt-get" ]; }
detect_best_method
assertEquals "apt-get -> apt" "apt" "$INSTALL_METHOD"
}
testDetectBestMethodForced() {
INSTALL_METHOD=apt
detect_best_method
assertEquals "forced method is preserved" "apt" "$INSTALL_METHOD"
}
testFindL10nPackageAptFrFr() {
DETECTED_LOCALE="fr-FR"
apt_cache_show_stub() {
[ "$1" = "firefox-l10n-fr-fr" ] && return 1
[ "$1" = "firefox-l10n-fr" ] && return 0
return 1
}
find_l10n_package "apt_cache_show_stub" "firefox"
assertEquals "fr-FR -> firefox-l10n-fr" "firefox-l10n-fr" "$_l10n_pkg"
}
testFindL10nPackageAptEnUS() {
DETECTED_LOCALE="en-US"
apt_cache_show_stub() {
[ "$1" = "firefox-l10n-fr-fr" ] && return 1
[ "$1" = "firefox-l10n-fr" ] && return 0
return 1
}
find_l10n_package "apt_cache_show_stub" "firefox"
assertEquals "en-US -> no l10n pkg" "" "$_l10n_pkg"
}
testAptSourceFormatDeb11() {
DISTRO=debian; DISTRO_VERSION=11
dpkg() { return 1; }; export -f dpkg # 11 < 13 → list
assertEquals "deb11 -> list" "list" "$(apt_source_format)"
}
testAptSourceFormatDeb12() {
DISTRO=debian; DISTRO_VERSION=12
dpkg() { return 1; }; export -f dpkg # 12 < 13 → list
assertEquals "deb12 -> list" "list" "$(apt_source_format)"
}
testAptSourceFormatDeb13() {
DISTRO=debian; DISTRO_VERSION=13
dpkg() { return 0; }; export -f dpkg # 13 >= 13 → deb822
assertEquals "deb13 -> deb822" "deb822" "$(apt_source_format)"
}
testAptSourceFormatUbuntu2404() {
DISTRO=ubuntu; DISTRO_VERSION=24.04
dpkg() { return 1; }; export -f dpkg # 24.04 < 25.10 → list
assertEquals "ubuntu 24.04 -> list" "list" "$(apt_source_format)"
}
testAptSourceFormatUbuntu2510() {
DISTRO=ubuntu; DISTRO_VERSION=25.10
dpkg() { return 0; }; export -f dpkg # 25.10 >= 25.10 → deb822
assertEquals "ubuntu 25.10 -> deb822" "deb822" "$(apt_source_format)"
}
testDetectOsLinuxX86_64() {
printf 'ID=debian\nVERSION_ID=12\nPRETTY_NAME="Debian 12"\n' > "${SHUNIT_TMPDIR}/os-release"
_OS_RELEASE="${SHUNIT_TMPDIR}/os-release"
uname() { case "$1" in -s) echo "Linux";; -m) echo "x86_64";; esac; }
export -f uname
detect_os
assertEquals "arch x86_64" "x86_64" "$ARCH"
assertEquals "distro debian" "debian" "$DISTRO"
assertEquals "version 12" "12" "$DISTRO_VERSION"
}
testDetectOsLinuxAarch64() {
printf 'ID=ubuntu\nVERSION_ID=20.04\nPRETTY_NAME="Ubuntu 20.04"\n' > "${SHUNIT_TMPDIR}/os-release"
_OS_RELEASE="${SHUNIT_TMPDIR}/os-release"
uname() { case "$1" in -s) echo "Linux";; -m) echo "aarch64";; esac; }
export -f uname
detect_os
assertEquals "arch aarch64" "aarch64" "$ARCH"
assertEquals "distro ubuntu" "ubuntu" "$DISTRO"
assertEquals "version 20.04" "20.04" "$DISTRO_VERSION"
}
testDetectOsNonLinuxErrors() {
uname() { case "$1" in -s) echo "Darwin";; -m) echo "x86_64";; esac; }
export -f uname
assertFalse "non-Linux OS errors" '( detect_os >/dev/null 2>&1 )'
}
testDetectOsUnknownArchErrors() {
printf 'ID=debian\nVERSION_ID=12\n' > "${SHUNIT_TMPDIR}/os-release"
_OS_RELEASE="${SHUNIT_TMPDIR}/os-release"
uname() { case "$1" in -s) echo "Linux";; -m) echo "i686";; esac; }
export -f uname
assertFalse "unknown arch errors" '( detect_os >/dev/null 2>&1 )'
}
testDetectOsNoOsRelease() {
_OS_RELEASE="${SHUNIT_TMPDIR}/nonexistent-os-release"
uname() { case "$1" in -s) echo "Linux";; -m) echo "x86_64";; esac; }
export -f uname
detect_os
assertEquals "distro unknown without os-release" "unknown" "$DISTRO"
}
testNeedCmdFound() {
assertTrue "need_cmd bash exits 0" 'need_cmd bash >/dev/null 2>&1'
}
testNeedCmdNotFound() {
assertFalse "need_cmd fails for missing command" '( need_cmd definitely_nonexistent_xyz >/dev/null 2>&1 )'
}
testRetrySucceeds() {
retry true
assertTrue "retry succeeds on first attempt" "$?"
}
testRetrySucceedsAfterOneFailure() {
sleep() { :; }; export -f sleep
local _flag="${SHUNIT_TMPDIR}/flag.tmp"
_flaky() { [ -f "$_flag" ] && return 0; touch "$_flag"; return 1; }
retry _flaky
assertTrue "retry succeeds after one failure" "$?"
}
testRetryExhaustedErrors() {
sleep() { :; }; export -f sleep
assertFalse "retry errors after exhausting attempts" '( retry false >/dev/null 2>&1 )'
}
testDownloadPrefersCurl() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
curl() { echo "curl $*" >> "$_calls"; return 0; }
export -f curl
download "https://example.com" "${SHUNIT_TMPDIR}/dl.out"
assertTrue "download prefers curl" "grep -q curl '$_calls'"
}
testDownloadFallsBackToWget() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
wget() { echo "wget $*" >> "$_calls"; return 0; }
export -f wget
check_cmd() { [ "$1" = "wget" ]; }
download "https://example.com" "${SHUNIT_TMPDIR}/dl.out"
assertTrue "download falls back to wget" "grep -q wget '$_calls'"
}
testDownloadError() {
check_cmd() { return 1; }; export -f check_cmd
assertFalse "download errors when no downloader available" \
'( download "https://example.com" /dev/null >/dev/null 2>&1 )'
}
testDownloadToStdoutPrefersCurl() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
curl() { echo "curl $*" >> "$_calls"; return 0; }
export -f curl
download_to_stdout "https://example.com" >/dev/null
assertTrue "download_to_stdout prefers curl" "grep -q curl '$_calls'"
}
testDownloadToStdoutWget() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
wget() { echo "wget $*" >> "$_calls"; return 0; }; export -f wget
check_cmd() { [ "$1" = "wget" ]; }; export -f check_cmd
download_to_stdout "https://example.com" >/dev/null
assertTrue "download_to_stdout falls back to wget" "grep -q wget '$_calls'"
}
testDownloadToStdoutError() {
check_cmd() { return 1; }; export -f check_cmd
assertFalse "download_to_stdout errors when no downloader available" \
'( download_to_stdout "https://example.com" >/dev/null 2>&1 )'
}
testRunSudoAsRoot() {
id() { echo "0"; }; export -f id
sudo() { fail "sudo should not be called as root"; }; export -f sudo
run_sudo true
assertTrue "run_sudo runs directly as root" "$?"
}
testRunSudoAsNonRoot() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
id() { echo "1000"; }; export -f id
sudo() { echo "sudo $*" >> "$_calls"; "$@"; }; export -f sudo
run_sudo echo "test" >/dev/null
assertTrue "run_sudo uses sudo as non-root" "grep -q sudo '$_calls'"
}
testVerifyInstallFindsFirefox() {
printf '#!/bin/sh\necho "Mozilla Firefox 100.0"\n' > "${SHUNIT_TMPDIR}/firefox"
chmod +x "${SHUNIT_TMPDIR}/firefox"
local _old_path="$PATH"
PATH="${SHUNIT_TMPDIR}:$PATH"
CHANNEL=release; INSTALL_METHOD=apt
verify_install 2>/dev/null
assertTrue "verify_install exits 0 when firefox found" "$?"
PATH="$_old_path"
}
testVerifyInstallWarnWhenMissing() {
CHANNEL=release; INSTALL_METHOD=apt
( PATH="/nonexistent_dir_xyz123" verify_install 2>/dev/null )
assertEquals "verify_install exits 0 even when not found" "0" "$?"
}
testInstallFirefoxDispatchApt() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
install_apt() { echo "install_apt" >> "$_calls"; }; export -f install_apt
INSTALL_METHOD=apt
install_firefox
assertTrue "install_firefox dispatches to apt" "grep -q install_apt '$_calls'"
}
testInstallFirefoxUnknownMethod() {
INSTALL_METHOD=bogus
assertFalse "unknown install method errors" '( install_firefox >/dev/null 2>&1 )'
}
testAptUseModernKeyringDebian11() {
DISTRO=debian DISTRO_VERSION=11
dpkg() { return 1; }; export -f dpkg # 11 not < 11 → modern
assertTrue "deb11 -> modern keyring" 'apt_use_modern_keyring'
}
testAptUseModernKeyringDebian9() {
DISTRO=debian DISTRO_VERSION=9
dpkg() { return 0; }; export -f dpkg # 9 < 11 → legacy
assertFalse "deb9 -> legacy keyring" 'apt_use_modern_keyring'
}
testAptUseModernKeyringUbuntu2204() {
DISTRO=ubuntu DISTRO_VERSION=22.04
dpkg() { return 1; }; export -f dpkg # 22.04 not < 22.04 → modern
assertTrue "ubuntu 22.04 -> modern keyring" 'apt_use_modern_keyring'
}
testAptUseModernKeyringUbuntu2004() {
DISTRO=ubuntu DISTRO_VERSION=20.04
dpkg() { return 0; }; export -f dpkg # 20.04 < 22.04 → legacy
assertFalse "ubuntu 20.04 -> legacy keyring" 'apt_use_modern_keyring'
}
testAptUseModernKeyringUnknownDistro() {
DISTRO=arch DISTRO_VERSION=
assertTrue "unknown distro -> modern keyring" 'apt_use_modern_keyring'
}
testAptVerifyGpgFingerprintMatch() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
gpg() { printf 'fpr:::::::::35BAA0B33E9EB396F59CA838C0BA5CE6DC6315A3:\n'; }
run_sudo() { echo "run_sudo $*" >> "$_calls"; }
export -f gpg run_sudo
apt_verify_gpg_fingerprint "/fake/keyring.asc"
assertFalse "matching fingerprint: keyring not removed" \
"grep -q 'run_sudo rm -f' '$_calls'"
}
testAptVerifyGpgFingerprintMismatch() {
gpg() { printf 'fpr:::::::::0000000000000000000000000000000000000000:\n'; }
run_sudo() { :; }
export -f gpg run_sudo
assertFalse "wrong fingerprint errors" \
'( apt_verify_gpg_fingerprint "/fake/keyring.asc" >/dev/null 2>&1 )'
}
testAptCleanupLegacySourcesRemovesKey() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
local _fake_key="${SHUNIT_TMPDIR}/packages.mozilla.org.gpg"
touch "$_fake_key"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
_APT_LEGACY_KEY="$_fake_key" apt_cleanup_legacy_sources >/dev/null 2>&1
assertTrue "legacy key removed" "grep -q 'run_sudo rm -f $_fake_key' '$_calls'"
}
testAptCleanupLegacySourcesRemovesListFile() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
local _fake_dir="${SHUNIT_TMPDIR}/sources.list.d"
mkdir -p "$_fake_dir"
printf 'deb https://packages.mozilla.org/apt mozilla main\n' > "$_fake_dir/mozilla.list"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
_APT_SOURCES_DIR="$_fake_dir" apt_cleanup_legacy_sources >/dev/null 2>&1
assertTrue "mozilla.list removed" "grep -q 'run_sudo rm -f' '$_calls'"
}
testAptWriteSourceEntryDeb822() {
DISTRO=debian DISTRO_VERSION=13
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
dpkg() { [ "$3" = "ge" ]; }; export -f dpkg
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
apt_write_source_entry "/etc/apt/keyrings/packages.mozilla.org.asc" "https://packages.mozilla.org/apt"
assertTrue "deb13: writes mozilla.sources" \
"grep -q 'run_sudo tee /etc/apt/sources.list.d/mozilla.sources' '$_calls'"
}
testAptWriteSourceEntryList() {
DISTRO=debian DISTRO_VERSION=12
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
dpkg() { return 1; }; export -f dpkg
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
apt_write_source_entry "/etc/apt/keyrings/packages.mozilla.org.asc" "https://packages.mozilla.org/apt"
assertTrue "deb12: writes mozilla.list" \
"grep -q 'run_sudo tee /etc/apt/sources.list.d/mozilla.list' '$_calls'"
}
testAptSetupModernKeyringDownloads() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
local _fake_tmpkey="${SHUNIT_TMPDIR}/fake.asc"
apt_cleanup_legacy_sources() { :; }; export -f apt_cleanup_legacy_sources
download() { echo "download $*" >> "$_calls"; touch "$2"; }; export -f download
check_cmd() { return 1; }; export -f check_cmd
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
mktemp() { echo "$_fake_tmpkey"; }; export -f mktemp
apt_write_source_entry() { echo "apt_write_source_entry $*" >> "$_calls"; }
export -f apt_write_source_entry
apt_setup_modern_keyring "https://key.url" "https://repo.url"
assertTrue "modern keyring: downloads key" \
"grep -q 'download https://key.url' '$_calls'"
assertTrue "modern keyring: installs to keyrings dir" \
"grep -q 'run_sudo install -m 644' '$_calls'"
assertTrue "modern keyring: writes source entry" \
"grep -q 'apt_write_source_entry' '$_calls'"
}
testAptSetupLegacyKeyring() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
local _fake_tmpkey="${SHUNIT_TMPDIR}/fake-legacy.asc"
download() { echo "download $*" >> "$_calls"; touch "$2"; }; export -f download
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
mktemp() { echo "$_fake_tmpkey"; }; export -f mktemp
apt_verify_gpg_fingerprint() { :; }; export -f apt_verify_gpg_fingerprint
apt_setup_legacy_keyring "https://key.url" "https://repo.url"
assertTrue "legacy keyring: downloads key" \
"grep -q 'download https://key.url' '$_calls'"
assertTrue "legacy keyring: apt-key add called" \
"grep -q 'run_sudo apt-key add' '$_calls'"
assertTrue "legacy keyring: mozilla.list written" \
"grep -q 'run_sudo tee /etc/apt/sources.list.d/mozilla.list' '$_calls'"
}
testAptAddPriorityPin() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
apt_add_priority_pin
assertTrue "apt priority pin: writes preferences.d/mozilla" \
"grep -q 'run_sudo tee /etc/apt/preferences.d/mozilla' '$_calls'"
}
testAptInstallPackages() {
CHANNEL=release DETECTED_LOCALE=en-US
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
find_l10n_package() { _l10n_pkg=""; }; export -f find_l10n_package
apt_install_packages "firefox"
assertTrue "apt install: apt-get update called" \
"grep -q 'run_sudo apt-get update' '$_calls'"
assertTrue "apt install: apt-get install called" \
"grep -q 'run_sudo apt-get install -y firefox$' '$_calls'"
}
testAptInstallPackagesWithL10n() {
CHANNEL=release DETECTED_LOCALE=fr-FR
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
find_l10n_package() { _l10n_pkg="firefox-l10n-fr"; }; export -f find_l10n_package
apt_install_packages "firefox"
assertTrue "apt install with l10n: both packages installed" \
"grep -q 'run_sudo apt-get install -y firefox firefox-l10n-fr' '$_calls'"
}
testInstallAptModernPath() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
apt_use_modern_keyring() { return 0; }
apt_setup_modern_keyring() { echo "apt_setup_modern_keyring $*" >> "$_calls"; }
apt_add_priority_pin() { echo "apt_add_priority_pin" >> "$_calls"; }
apt_install_packages() { echo "apt_install_packages $*" >> "$_calls"; }
check_url() { return 0; }; need_cmd() { return 0; }
export -f apt_use_modern_keyring apt_setup_modern_keyring apt_add_priority_pin \
apt_install_packages check_url need_cmd
install_apt
assertTrue "modern path: setup_modern_keyring called" \
"grep -q apt_setup_modern_keyring '$_calls'"
}
testInstallAptLegacyPath() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
apt_use_modern_keyring() { return 1; }
apt_setup_legacy_keyring() { echo "apt_setup_legacy_keyring $*" >> "$_calls"; }
apt_add_priority_pin() { echo "apt_add_priority_pin" >> "$_calls"; }
apt_install_packages() { echo "apt_install_packages $*" >> "$_calls"; }
check_url() { return 0; }; need_cmd() { return 0; }
export -f apt_use_modern_keyring apt_setup_legacy_keyring apt_add_priority_pin \
apt_install_packages check_url need_cmd
install_apt
assertTrue "legacy path: setup_legacy_keyring called" \
"grep -q apt_setup_legacy_keyring '$_calls'"
}
testParseArgsInstallMethodRpm() {
parse_args --install-method rpm
assertEquals "method=rpm" "rpm" "$INSTALL_METHOD"
}
testDetectBestMethodDnf() {
check_cmd() { [ "$1" = "dnf" ]; }
detect_best_method
assertEquals "dnf -> rpm" "rpm" "$INSTALL_METHOD"
}
testDetectBestMethodYum() {
check_cmd() { [ "$1" = "yum" ]; }
detect_best_method
assertEquals "yum -> tarball (yum can't use rpm method)" "tarball" "$INSTALL_METHOD"
}
testDetectBestMethodZypper() {
check_cmd() { [ "$1" = "zypper" ]; }
detect_best_method
assertEquals "zypper -> rpm" "rpm" "$INSTALL_METHOD"
}
testFindL10nPackageRpm() {
DETECTED_LOCALE="fr"
_rpm_l10n_stub() { [ "$1" = "firefox-l10n-fr" ] && return 0; return 1; }
find_l10n_package _rpm_l10n_stub "firefox"
assertEquals "fr -> firefox-l10n-fr (dnf)" "firefox-l10n-fr" "$_l10n_pkg"
}
testInstallFirefoxDispatchRpm() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
install_rpm() { echo "install_rpm" >> "$_calls"; }; export -f install_rpm
INSTALL_METHOD=rpm
install_firefox
assertTrue "install_firefox dispatches to rpm" "grep -q install_rpm '$_calls'"
}
testRpmPkgExistsDnf() {
dnf() { return 0; }; export -f dnf
assertTrue "rpm_pkg_exists dnf: found" 'rpm_pkg_exists dnf firefox'
}
testRpmPkgExistsDnfMissing() {
dnf() { return 1; }; export -f dnf
assertFalse "rpm_pkg_exists dnf: missing" 'rpm_pkg_exists dnf nonexistent'
}
testRpmPkgExistsZypper() {
zypper() { return 0; }; export -f zypper
assertTrue "rpm_pkg_exists zypper: found" 'rpm_pkg_exists zypper firefox'
}
testRpmPkgExistsUnknown() {
assertFalse "rpm_pkg_exists unknown manager: returns 1" 'rpm_pkg_exists snap firefox'
}
testRpmDetectPkgManagerDnf() {
check_cmd() { [ "$1" = "dnf" ]; }; export -f check_cmd
assertEquals "dnf detected" "dnf" "$(rpm_detect_pkg_manager)"
}
testRpmDetectPkgManagerYumErrors() {
check_cmd() { [ "$1" = "yum" ]; }; export -f check_cmd
assertFalse "yum errors (not supported)" '( rpm_detect_pkg_manager >/dev/null 2>&1 )'
}
testRpmDetectPkgManagerZypper() {
check_cmd() { [ "$1" = "zypper" ]; }; export -f check_cmd
assertEquals "zypper detected" "zypper" "$(rpm_detect_pkg_manager)"
}
testRpmDetectPkgManagerNone() {
check_cmd() { return 1; }; export -f check_cmd
assertFalse "no rpm manager errors" '( rpm_detect_pkg_manager >/dev/null 2>&1 )'
}
testRpmAddRepositoryDnf() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
rpm_add_repository "dnf" "https://repo.url" "https://gpg.url"
assertTrue "dnf: repo file written" \
"grep -q 'run_sudo tee /etc/yum.repos.d/mozilla-firefox.repo' '$_calls'"
}
testRpmAddRepositoryZypper() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
zypper() { echo "zypper $*" >> "$_calls"; return 1; }; export -f zypper
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
rpm_add_repository "zypper" "https://repo.url" "https://gpg.url"
assertTrue "zypper: zypper ar called" \
"grep -q 'run_sudo zypper ar' '$_calls'"
assertTrue "zypper: refresh called" \
"grep -q 'run_sudo zypper --gpg-auto-import-keys refresh' '$_calls'"
}
testRpmInstallPackagesDnf() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
rpm_install_packages "dnf" "firefox" ""
assertTrue "dnf install called" \
"grep -q 'run_sudo dnf install -y firefox' '$_calls'"
}
testRpmInstallPackagesZypper() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
rpm_install_packages "zypper" "firefox" ""
assertTrue "zypper install --replacefiles called" \
"grep -q 'run_sudo zypper install -y --replacefiles firefox' '$_calls'"
}
testRpmInstallPackagesWithL10n() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
rpm_install_packages "dnf" "firefox" "firefox-l10n-fr"
assertTrue "l10n package installed" \
"grep -q 'run_sudo dnf install -y firefox-l10n-fr' '$_calls'"
}
testRpmInstallPackagesL10nFallback() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; case "$*" in *l10n*) return 1;; esac; }
export -f run_sudo
retry() { "$@"; }; export -f retry
rpm_install_packages "dnf" "firefox" "firefox-l10n-fr"
assertTrue "main package still installed on l10n failure" \
"grep -q 'run_sudo dnf install -y firefox' '$_calls'"
}
testInstallRpmOrchestration() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
rpm_detect_pkg_manager() { echo "dnf"; }
rpm_add_repository() { echo "rpm_add_repository $*" >> "$_calls"; }
rpm_install_packages() { echo "rpm_install_packages $*" >> "$_calls"; }
check_url() { return 0; }; find_l10n_package() { _l10n_pkg=""; }
export -f rpm_detect_pkg_manager rpm_add_repository rpm_install_packages \
check_url find_l10n_package
CHANNEL=release
install_rpm
assertTrue "rpm_add_repository called" "grep -q rpm_add_repository '$_calls'"
assertTrue "rpm_install_packages called" "grep -q rpm_install_packages '$_calls'"
}
testParseArgsInstallMethodFlatpak() {
parse_args --install-method flatpak
assertEquals "method=flatpak" "flatpak" "$INSTALL_METHOD"
}
testDetectBestMethodFlatpak() {
check_cmd() { [ "$1" = "flatpak" ]; }
detect_best_method
assertEquals "flatpak -> flatpak" "flatpak" "$INSTALL_METHOD"
}
testChannelToFlatpakBranchRelease() {
CHANNEL=release
local _result
_result="$(channel_to_flatpak_branch)"
assertEquals "flatpak branch release" "" "$_result"
}
testChannelToFlatpakBranchBeta() {
CHANNEL=beta
assertEquals "flatpak branch beta" "beta" "$(channel_to_flatpak_branch)"
}
testChannelToFlatpakBranchDeveditionErrors() {
assertFalse "flatpak devedition errors" 'sh "$INSTALL_FIREFOX_SCRIPT" --channel devedition --install-method flatpak >/dev/null 2>&1'
}
testChannelToFlatpakBranchNightlyErrors() {
assertFalse "flatpak nightly errors" 'sh "$INSTALL_FIREFOX_SCRIPT" --channel nightly --install-method flatpak >/dev/null 2>&1'
}
testFlatpakAddRemotesRelease() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
flatpak_add_remotes ""
assertTrue "release: flathub added" \
"grep -q 'run_sudo flatpak remote-add --if-not-exists --system flathub ' '$_calls'"
assertFalse "release: flathub-beta not added" \
"grep -q 'flathub-beta' '$_calls'"
}
testFlatpakAddRemotesBeta() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
retry() { "$@"; }; export -f retry
flatpak_add_remotes "beta"
assertTrue "beta: flathub-beta added" \
"grep -q 'run_sudo flatpak remote-add --if-not-exists --system flathub-beta ' '$_calls'"
}
testFlatpakInstallOrUpdateFresh() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
flatpak() { echo "flatpak $*" >> "$_calls"; [ "$1" = "info" ] && return 1; return 0; }
run_sudo() { echo "run_sudo $*" >> "$_calls"; }
retry() { "$@"; }
export -f flatpak run_sudo retry
flatpak_install_or_update ""
assertTrue "fresh install: flatpak install called" \
"grep -q 'run_sudo flatpak install --system -y flathub org.mozilla.firefox' '$_calls'"
}
testFlatpakInstallOrUpdateExisting() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
flatpak() { echo "flatpak $*" >> "$_calls"; return 0; }
run_sudo() { echo "run_sudo $*" >> "$_calls"; }
retry() { "$@"; }
export -f flatpak run_sudo retry
flatpak_install_or_update "beta"
assertTrue "already installed: flatpak update called" \
"grep -q 'run_sudo flatpak update --system -y org.mozilla.firefox//beta' '$_calls'"
}
testInstallFlatpakOrchestration() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
flatpak_add_remotes() { echo "flatpak_add_remotes $*" >> "$_calls"; }
flatpak_install_or_update() { echo "flatpak_install_or_update $*" >> "$_calls"; }
need_cmd() { return 0; }
export -f flatpak_add_remotes flatpak_install_or_update need_cmd
CHANNEL=release install_flatpak
assertTrue "add_remotes called" "grep -q flatpak_add_remotes '$_calls'"
assertTrue "install_or_update called" "grep -q flatpak_install_or_update '$_calls'"
}
testInstallFirefoxDispatchFlatpak() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
install_flatpak() { echo "install_flatpak" >> "$_calls"; }; export -f install_flatpak
INSTALL_METHOD=flatpak
install_firefox
assertTrue "install_firefox dispatches to flatpak" "grep -q install_flatpak '$_calls'"
}
testParseArgsInstallMethodTarball() {
parse_args --install-method tarball
assertEquals "method=tarball" "tarball" "$INSTALL_METHOD"
}
testTarballProductNameRelease() {
CHANNEL=release
assertEquals "tarball release" "firefox-latest-ssl" "$(tarball_product_name)"
}
testTarballProductNameBeta() {
CHANNEL=beta
assertEquals "tarball beta" "firefox-beta-latest-ssl" "$(tarball_product_name)"
}
testTarballProductNameDevedition() {
CHANNEL=devedition
assertEquals "tarball devedition" "firefox-devedition-latest-ssl" "$(tarball_product_name)"
}
testTarballProductNameNightly() {
CHANNEL=nightly
assertEquals "tarball nightly" "firefox-nightly-latest-ssl" "$(tarball_product_name)"
}
testDetectBestMethodTarball() {
check_cmd() { return 1; }
detect_best_method
assertEquals "no package manager -> tarball" "tarball" "$INSTALL_METHOD"
}
testInstallFirefoxDispatchTarball() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
install_tarball() { echo "install_tarball" >> "$_calls"; }; export -f install_tarball
INSTALL_METHOD=tarball
install_firefox
assertTrue "install_firefox dispatches to tarball" "grep -q install_tarball '$_calls'"
}
testTarballBuildUrlX86_64() {
ARCH=x86_64 CHANNEL=release DETECTED_LOCALE=en-US
local _url
_url="$(tarball_build_url)"
assertTrue "x86_64: URL contains linux64" \
"echo '$_url' | grep -q 'os=linux64'"
assertFalse "x86_64: URL does not contain aarch64" \
"echo '$_url' | grep -q 'aarch64'"
}
testTarballBuildUrlAarch64() {
ARCH=aarch64 CHANNEL=release DETECTED_LOCALE=en-US
local _url
_url="$(tarball_build_url)"
assertTrue "aarch64: URL contains linux64-aarch64" \
"echo '$_url' | grep -q 'os=linux64-aarch64'"
}
testTarballBuildUrlUnsupportedArch() {
ARCH=i686 CHANNEL=release DETECTED_LOCALE=en-US
assertFalse "unsupported arch errors" '( tarball_build_url >/dev/null 2>&1 )'
}
testTarballResolvesFinalUrlCurl() {
curl() { echo "https://final.example.com/firefox.tar"; return 0; }
export -f curl
result="$(tarball_resolve_final_url "https://download.example.com/firefox")"
assertEquals "curl resolves final URL" "https://final.example.com/firefox.tar" "$result"
}
testTarballResolvesFinalUrlWget() {
wget() {
return 0
}
export -f wget
check_cmd() { [ "$1" = "wget" ]; }
result="$(tarball_resolve_final_url "https://download.example.com/firefox")"
assertEquals "wget resolves final URL" "https://final.example.com/firefox.tar" "$result"
}
testTarballKeyUrlRelease() {
result="$(tarball_key_url \
assertEquals "release key URL" \
}
testTarballKeyUrlNightly() {
result="$(tarball_key_url \
assertEquals "nightly key URL" \
}
testTarballKeyUrlUnknown() {
result="$(tarball_key_url "https://example.com/some/path/firefox.tar.xz")"
assertEquals "unknown URL returns empty" "" "$result"
}
testTarballVerifyGpgSuccess() {
local _gpgdir="${SHUNIT_TMPDIR}/gpg-ok"
mkdir -p "$_gpgdir"
download_to_stdout() { echo "fake-key"; }
download() { touch "$2"; }
gpg() {
case "$*" in
*--list-keys*) printf 'fpr:::::::::14F26682D0916CDD81E37B6D61B7B526D98F0353:\n' ;;
*) return 0 ;;
esac
}
export -f download_to_stdout download gpg
assertTrue "gpg verify succeeds" \
"( tarball_verify_gpg /dev/null \"https://example.com/firefox.tar.asc\" \"https://example.com/KEY\" \"$_gpgdir\" >/dev/null 2>&1 )"
}
testTarballVerifyGpgFailure() {
local _gpgdir="${SHUNIT_TMPDIR}/gpg-fail"
mkdir -p "$_gpgdir"
download_to_stdout() { echo "fake-key"; }
download() { touch "$2"; }
gpg() {
case "$*" in
*--list-keys*) printf 'fpr:::::::::14F26682D0916CDD81E37B6D61B7B526D98F0353:\n' ;;
*--verify*) return 1 ;;
*) return 0 ;;
esac
}
export -f download_to_stdout download gpg
assertFalse "gpg verify failure errors" \
"( tarball_verify_gpg /dev/null \"https://example.com/firefox.tar.asc\" \"https://example.com/KEY\" \"$_gpgdir\" >/dev/null 2>&1 )"
}
testTarballVerifyGpgBadImport() {
local _gpgdir="${SHUNIT_TMPDIR}/gpg-bad"
mkdir -p "$_gpgdir"
download_to_stdout() { echo "bad-key"; }
download() { touch "$2"; }
# import succeeds but list-keys returns wrong fingerprint
gpg() {
case "$*" in
*--list-keys*) printf 'fpr:::::::::0000000000000000000000000000000000000000:\n' ;;
*) return 0 ;;
esac
}
export -f download_to_stdout download gpg
assertFalse "bad import detected by fingerprint check" \
"( tarball_verify_gpg /dev/null \"https://example.com/firefox.tar.asc\" \"https://example.com/KEY\" \"$_gpgdir\" >/dev/null 2>&1 )"
}
testTarballDownloadAndExtract() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
local _tmpdir="${SHUNIT_TMPDIR}/tbtest"
mkdir -p "$_tmpdir"
download() { echo "download $*" >> "$_calls"; touch "$2"; }
tar() { echo "tar $*" >> "$_calls"; }
tarball_verify_gpg() { echo "tarball_verify_gpg $*" >> "$_calls"; }
check_cmd() { [ "$1" = "gpg" ]; }
export -f download tar tarball_resolve_final_url tarball_verify_gpg check_cmd
tarball_download_and_extract "https://example.com/firefox.tar" "$_tmpdir"
assertTrue "download uses resolved final URL" "grep -q 'download https://releases.mozilla.org' '$_calls'"
assertTrue "tar -xf called" "grep -q 'tar -xf' '$_calls'"
assertTrue "gpg verify called" "grep -q tarball_verify_gpg '$_calls'"
}
testTarballInstallTo() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
local _tmpdir="${SHUNIT_TMPDIR}/tarball-install"
mkdir -p "$_tmpdir/firefox"
run_sudo() { echo "run_sudo $*" >> "$_calls"; }; export -f run_sudo
tarball_install_to "$_tmpdir" "/opt/firefox" "firefox"
assertTrue "mv extracted dir to install_dir" \
"grep -q 'run_sudo mv $_tmpdir/firefox /opt/firefox' '$_calls'"
assertTrue "symlink created" \
"grep -q 'run_sudo ln -sf /opt/firefox/firefox /usr/local/bin/firefox' '$_calls'"
}
testInstallTarballOrchestration() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
tarball_build_url() { echo "https://fake.url/firefox.tar"; }
tarball_download_and_extract() { echo "tarball_download_and_extract $*" >> "$_calls"; }
tarball_install_to() { echo "tarball_install_to $*" >> "$_calls"; }
need_cmd() { return 0; }
export -f tarball_build_url tarball_download_and_extract tarball_install_to need_cmd
CHANNEL=release install_tarball
assertTrue "download_and_extract called" \
"grep -q tarball_download_and_extract '$_calls'"
assertTrue "install_to called" "grep -q tarball_install_to '$_calls'"
}
testParseArgsInstallMethodSnap() {
parse_args --install-method snap
assertEquals "method=snap" "snap" "$INSTALL_METHOD"
}
testChannelToSnapChannelRelease() {
CHANNEL=release
assertEquals "snap release -> stable" "stable" "$(channel_to_snap_channel)"
}
testChannelToSnapChannelBeta() {
CHANNEL=beta
assertEquals "snap beta -> beta" "beta" "$(channel_to_snap_channel)"
}
testChannelToSnapChannelNightly() {
CHANNEL=nightly
assertEquals "snap nightly -> edge" "edge" "$(channel_to_snap_channel)"
}
testChannelToSnapChannelDeveditionErrors() {
assertFalse "snap devedition errors" \
'( CHANNEL=devedition channel_to_snap_channel >/dev/null 2>&1 )'
}
testDetectBestMethodSnap() {
check_cmd() { [ "$1" = "snap" ]; }; export -f check_cmd
detect_best_method
assertEquals "snap -> snap" "snap" "$INSTALL_METHOD"
}
testDetectBestMethodSnapOverFlatpak() {
check_cmd() { [ "$1" = "snap" ] || [ "$1" = "flatpak" ]; }; export -f check_cmd
detect_best_method
assertEquals "snap beats flatpak when both present" "snap" "$INSTALL_METHOD"
}
testInstallSnapFreshInstall() {
CHANNEL=release
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
snap() { echo "snap $*" >> "$_calls"; [ "$1" = "list" ] && return 1; return 0; }
run_sudo() { echo "run_sudo $*" >> "$_calls"; }
retry() { "$@"; }
need_cmd() { return 0; }
export -f snap run_sudo retry need_cmd
install_snap
assertTrue "fresh install: snap install called" \
"grep -q 'run_sudo snap install firefox --channel=stable' '$_calls'"
}
testInstallSnapAlreadyInstalled() {
CHANNEL=beta
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
snap() { echo "snap $*" >> "$_calls"; return 0; }
run_sudo() { echo "run_sudo $*" >> "$_calls"; }
retry() { "$@"; }
need_cmd() { return 0; }
export -f snap run_sudo retry need_cmd
install_snap
assertTrue "already installed: snap refresh called" \
"grep -q 'run_sudo snap refresh firefox --channel=beta' '$_calls'"
}
testInstallFirefoxDispatchSnap() {
local _calls="${SHUNIT_TMPDIR}/calls.tmp"
install_snap() { echo "install_snap" >> "$_calls"; }; export -f install_snap
INSTALL_METHOD=snap
install_firefox
assertTrue "install_firefox dispatches to snap" "grep -q install_snap '$_calls'"
}
. shunit2