summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozinstall
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/mozbase/mozinstall
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--testing/mozbase/mozinstall/mozinstall/__init__.py6
-rw-r--r--testing/mozbase/mozinstall/mozinstall/mozinstall.py443
-rw-r--r--testing/mozbase/mozinstall/setup.cfg2
-rw-r--r--testing/mozbase/mozinstall/setup.py59
-rw-r--r--testing/mozbase/mozinstall/tests/conftest.py14
-rw-r--r--testing/mozbase/mozinstall/tests/installer_stubs/firefox.dmgbin0 -> 27309 bytes
-rw-r--r--testing/mozbase/mozinstall/tests/installer_stubs/firefox.tar.bz2bin0 -> 2882 bytes
-rw-r--r--testing/mozbase/mozinstall/tests/installer_stubs/firefox.zipbin0 -> 8707 bytes
-rw-r--r--testing/mozbase/mozinstall/tests/manifest.ini8
-rw-r--r--testing/mozbase/mozinstall/tests/test_binary.py50
-rw-r--r--testing/mozbase/mozinstall/tests/test_install.py90
-rw-r--r--testing/mozbase/mozinstall/tests/test_is_installer.py40
-rw-r--r--testing/mozbase/mozinstall/tests/test_uninstall.py39
13 files changed, 751 insertions, 0 deletions
diff --git a/testing/mozbase/mozinstall/mozinstall/__init__.py b/testing/mozbase/mozinstall/mozinstall/__init__.py
new file mode 100644
index 0000000000..09c6d10a3d
--- /dev/null
+++ b/testing/mozbase/mozinstall/mozinstall/__init__.py
@@ -0,0 +1,6 @@
+# flake8: noqa
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from .mozinstall import *
diff --git a/testing/mozbase/mozinstall/mozinstall/mozinstall.py b/testing/mozbase/mozinstall/mozinstall/mozinstall.py
new file mode 100644
index 0000000000..d966b258ed
--- /dev/null
+++ b/testing/mozbase/mozinstall/mozinstall/mozinstall.py
@@ -0,0 +1,443 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import plistlib
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+import time
+import zipfile
+from optparse import OptionParser
+
+import mozfile
+import mozinfo
+import requests
+from six import PY3, reraise
+
+try:
+ import pefile
+
+ has_pefile = True
+except ImportError:
+ has_pefile = False
+
+
+TIMEOUT_UNINSTALL = 60
+
+
+class InstallError(Exception):
+ """Thrown when installation fails. Includes traceback if available."""
+
+
+class InvalidBinary(Exception):
+ """Thrown when the binary cannot be found after the installation."""
+
+
+class InvalidSource(Exception):
+ """Thrown when the specified source is not a recognized file type.
+
+ Supported types:
+ Linux: tar.gz, tar.bz2
+ Mac: dmg
+ Windows: zip, exe
+
+ """
+
+
+class UninstallError(Exception):
+ """Thrown when uninstallation fails. Includes traceback if available."""
+
+
+def _readPlist(path):
+ if PY3:
+ with open(path, "rb") as fp:
+ return plistlib.load(fp)
+ return plistlib.readPlist(path)
+
+
+def get_binary(path, app_name):
+ """Find the binary in the specified path, and return its path. If binary is
+ not found throw an InvalidBinary exception.
+
+ :param path: Path within to search for the binary
+ :param app_name: Application binary without file extension to look for
+ """
+ binary = None
+
+ # On OS X we can get the real binary from the app bundle
+ if mozinfo.isMac:
+ plist = "%s/Contents/Info.plist" % path
+ if not os.path.isfile(plist):
+ raise InvalidBinary("%s/Contents/Info.plist not found" % path)
+
+ binary = os.path.join(
+ path, "Contents/MacOS/", _readPlist(plist)["CFBundleExecutable"]
+ )
+
+ else:
+ app_name = app_name.lower()
+
+ if mozinfo.isWin:
+ app_name = app_name + ".exe"
+
+ for root, dirs, files in os.walk(path):
+ for filename in files:
+ # os.access evaluates to False for some reason, so not using it
+ if filename.lower() == app_name:
+ binary = os.path.realpath(os.path.join(root, filename))
+ break
+
+ if not binary:
+ # The expected binary has not been found.
+ raise InvalidBinary('"%s" does not contain a valid binary.' % path)
+
+ return binary
+
+
+def install(src, dest):
+ """Install a zip, exe, tar.gz, tar.bz2 or dmg file, and return the path of
+ the installation folder.
+
+ :param src: Path to the install file
+ :param dest: Path to install to (to ensure we do not overwrite any existent
+ files the folder should not exist yet)
+ """
+ if not is_installer(src):
+ msg = "{} is not a valid installer file".format(src)
+ if "://" in src:
+ try:
+ return _install_url(src, dest)
+ except Exception:
+ exc, val, tb = sys.exc_info()
+ error = InvalidSource("{} ({})".format(msg, val))
+ reraise(InvalidSource, error, tb)
+ raise InvalidSource(msg)
+
+ src = os.path.realpath(src)
+ dest = os.path.realpath(dest)
+
+ did_we_create = False
+ if not os.path.exists(dest):
+ did_we_create = True
+ os.makedirs(dest)
+
+ trbk = None
+ try:
+ install_dir = None
+ if src.lower().endswith(".dmg"):
+ install_dir = _install_dmg(src, dest)
+ elif src.lower().endswith(".exe"):
+ install_dir = _install_exe(src, dest)
+ elif src.lower().endswith(".msix"):
+ install_dir = _install_msix(src)
+ elif zipfile.is_zipfile(src) or tarfile.is_tarfile(src):
+ install_dir = mozfile.extract(src, dest)[0]
+
+ return install_dir
+
+ except BaseException:
+ cls, exc, trbk = sys.exc_info()
+ if did_we_create:
+ try:
+ # try to uninstall this properly
+ uninstall(dest)
+ except Exception:
+ # uninstall may fail, let's just try to clean the folder
+ # in this case
+ try:
+ mozfile.remove(dest)
+ except Exception:
+ pass
+ if issubclass(cls, Exception):
+ error = InstallError('Failed to install "%s (%s)"' % (src, str(exc)))
+ reraise(InstallError, error, trbk)
+ # any other kind of exception like KeyboardInterrupt is just re-raised.
+ reraise(cls, exc, trbk)
+
+ finally:
+ # trbk won't get GC'ed due to circular reference
+ # http://docs.python.org/library/sys.html#sys.exc_info
+ del trbk
+
+
+def is_installer(src):
+ """Tests if the given file is a valid installer package.
+
+ Supported types:
+ Linux: tar.gz, tar.bz2
+ Mac: dmg
+ Windows: zip, exe
+
+ On Windows pefile will be used to determine if the executable is the
+ right type, if it is installed on the system.
+
+ :param src: Path to the install file.
+ """
+ src = os.path.realpath(src)
+
+ if not os.path.isfile(src):
+ return False
+
+ if mozinfo.isLinux:
+ return tarfile.is_tarfile(src)
+ elif mozinfo.isMac:
+ return src.lower().endswith(".dmg")
+ elif mozinfo.isWin:
+ if zipfile.is_zipfile(src):
+ return True
+
+ if os.access(src, os.X_OK) and src.lower().endswith(".exe"):
+ if has_pefile:
+ # try to determine if binary is actually a gecko installer
+ pe_data = pefile.PE(src)
+ data = {}
+ for info in getattr(pe_data, "FileInfo", []):
+ if info.Key == "StringFileInfo":
+ for string in info.StringTable:
+ data.update(string.entries)
+ return "BuildID" not in data
+ else:
+ # pefile not available, just assume a proper binary was passed in
+ return True
+
+ return False
+
+
+def uninstall(install_folder):
+ """Uninstalls the application in the specified path. If it has been
+ installed via an installer on Windows, use the uninstaller first.
+
+ :param install_folder: Path of the installation folder
+
+ """
+ # Uninstallation for MSIX applications is totally different than
+ # any other installs...
+ if "WindowsApps" in install_folder:
+ # At the time of writing, the package installation directory is always
+ # the package full name, so this assumption is valid (for now....).
+ packageFullName = install_folder.split("WindowsApps\\")[1].split("\\")[0]
+ cmd = f"powershell.exe Remove-AppxPackage -Package {packageFullName}"
+ subprocess.check_call(cmd)
+ return
+
+ install_folder = os.path.realpath(install_folder)
+ assert os.path.isdir(install_folder), (
+ 'installation folder "%s" exists.' % install_folder
+ )
+
+ # On Windows we have to use the uninstaller. If it's not available fallback
+ # to the directory removal code
+ if mozinfo.isWin:
+ uninstall_folder = "%s\\uninstall" % install_folder
+ log_file = "%s\\uninstall.log" % uninstall_folder
+
+ if os.path.isfile(log_file):
+ trbk = None
+ try:
+ cmdArgs = ["%s\\uninstall\helper.exe" % install_folder, "/S"]
+ result = subprocess.call(cmdArgs)
+ if result != 0:
+ raise Exception("Execution of uninstaller failed.")
+
+ # The uninstaller spawns another process so the subprocess call
+ # returns immediately. We have to wait until the uninstall
+ # folder has been removed or until we run into a timeout.
+ end_time = time.time() + TIMEOUT_UNINSTALL
+ while os.path.exists(uninstall_folder):
+ time.sleep(1)
+
+ if time.time() > end_time:
+ raise Exception("Failure removing uninstall folder.")
+
+ except Exception as ex:
+ cls, exc, trbk = sys.exc_info()
+ error = UninstallError(
+ "Failed to uninstall %s (%s)" % (install_folder, str(ex))
+ )
+ reraise(UninstallError, error, trbk)
+
+ finally:
+ # trbk won't get GC'ed due to circular reference
+ # http://docs.python.org/library/sys.html#sys.exc_info
+ del trbk
+
+ # Ensure that we remove any trace of the installation. Even the uninstaller
+ # on Windows leaves files behind we have to explicitely remove.
+ mozfile.remove(install_folder)
+
+
+def _install_url(url, dest):
+ """Saves a url to a temporary file, and passes that through to the
+ install function.
+
+ :param url: Url to the install file
+ :param dest: Path to install to (to ensure we do not overwrite any existent
+ files the folder should not exist yet)
+ """
+ r = requests.get(url, stream=True)
+ name = tempfile.mkstemp()[1]
+ try:
+ with open(name, "w+b") as fh:
+ for chunk in r.iter_content(chunk_size=16 * 1024):
+ fh.write(chunk)
+ result = install(name, dest)
+ finally:
+ mozfile.remove(name)
+ return result
+
+
+def _install_dmg(src, dest):
+ """Extract a dmg file into the destination folder and return the
+ application folder.
+
+ src -- DMG image which has to be extracted
+ dest -- the path to extract to
+
+ """
+ appDir = None
+ try:
+ # According to the Apple doc, the hdiutil output is stable and is based on the tab
+ # separators
+ # Therefor, $3 should give us the mounted path
+ appDir = (
+ subprocess.check_output(
+ 'hdiutil attach -nobrowse -noautoopen "%s"'
+ "|grep /Volumes/"
+ "|awk 'BEGIN{FS=\"\t\"} {print $3}'" % str(src),
+ shell=True,
+ )
+ .strip()
+ .decode("ascii")
+ )
+
+ for appFile in os.listdir(appDir):
+ if appFile.endswith(".app"):
+ appName = appFile
+ break
+
+ mounted_path = os.path.join(appDir, appName)
+
+ dest = os.path.join(dest, appName)
+
+ # copytree() would fail if dest already exists.
+ if os.path.exists(dest):
+ raise InstallError('App bundle "%s" already exists.' % dest)
+
+ shutil.copytree(mounted_path, dest, False)
+
+ finally:
+ if appDir:
+ subprocess.check_call('hdiutil detach "%s" -quiet' % appDir, shell=True)
+
+ return dest
+
+
+def _install_exe(src, dest):
+ """Run the MSI installer to silently install the application into the
+ destination folder. Return the folder path.
+
+ Arguments:
+ src -- MSI installer to be executed
+ dest -- the path to install to
+
+ """
+ # The installer doesn't automatically create a sub folder. Lets guess the
+ # best name from the src file name
+ filename = os.path.basename(src)
+ dest = os.path.join(dest, filename.split(".")[0])
+
+ # possibly gets around UAC in vista (still need to run as administrator)
+ os.environ["__compat_layer"] = "RunAsInvoker"
+ cmd = '"%s" /extractdir=%s' % (src, os.path.realpath(dest))
+
+ subprocess.check_call(cmd)
+
+ return dest
+
+
+def _get_msix_install_location(pkg):
+ with zipfile.ZipFile(pkg) as zf:
+ # First, we pull the app identity out of the AppxManifest...
+ with zf.open("AppxManifest.xml") as am:
+ for line in am.readlines():
+ line = line.decode("utf-8")
+ if "<Identity" in line:
+ for part in line.split(" "):
+ if part.startswith("Name"):
+ pkgname = part.split("=")[-1].strip('"\r\n')
+
+ # ...then we can use it to find the install location
+ # with this cmdlet
+ cmd = (
+ f'powershell.exe "Get-AppxPackage" "-Name" "{pkgname}"'
+ )
+ for line in (
+ subprocess.check_output(cmd)
+ .decode("utf-8")
+ .splitlines()
+ ):
+ if line.startswith("InstallLocation"):
+ return "C:{}".format(line.split(":")[-1].strip())
+
+ raise Exception(f"Couldn't find install location of {pkg}")
+
+
+def _install_msix(src):
+ """Install the MSIX package and return the installation path.
+
+ Arguments:
+ src -- MSIX package to install
+
+ """
+ # possibly gets around UAC in vista (still need to run as administrator)
+ cmd = f'powershell.exe "Add-AppxPackage" "-Path" "{src}"'
+ subprocess.check_call(cmd)
+
+ return _get_msix_install_location(src)
+
+
+def install_cli(argv=sys.argv[1:]):
+ parser = OptionParser(usage="usage: %prog [options] installer")
+ parser.add_option(
+ "-d",
+ "--destination",
+ dest="dest",
+ default=os.getcwd(),
+ help="Directory to install application into. " '[default: "%default"]',
+ )
+ parser.add_option(
+ "--app",
+ dest="app",
+ default="firefox",
+ help="Application being installed. [default: %default]",
+ )
+
+ (options, args) = parser.parse_args(argv)
+ if not len(args) == 1:
+ parser.error("An installer file has to be specified.")
+
+ src = args[0]
+
+ # Run it
+ if os.path.isdir(src):
+ binary = get_binary(src, app_name=options.app)
+ else:
+ install_path = install(src, options.dest)
+ binary = get_binary(install_path, app_name=options.app)
+
+ print(binary)
+
+
+def uninstall_cli(argv=sys.argv[1:]):
+ parser = OptionParser(usage="usage: %prog install_path")
+
+ (options, args) = parser.parse_args(argv)
+ if not len(args) == 1:
+ parser.error("An installation path has to be specified.")
+
+ # Run it
+ uninstall(argv[0])
diff --git a/testing/mozbase/mozinstall/setup.cfg b/testing/mozbase/mozinstall/setup.cfg
new file mode 100644
index 0000000000..2a9acf13da
--- /dev/null
+++ b/testing/mozbase/mozinstall/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal = 1
diff --git a/testing/mozbase/mozinstall/setup.py b/testing/mozbase/mozinstall/setup.py
new file mode 100644
index 0000000000..dbcf2a1c19
--- /dev/null
+++ b/testing/mozbase/mozinstall/setup.py
@@ -0,0 +1,59 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+from setuptools import setup
+
+try:
+ here = os.path.dirname(os.path.abspath(__file__))
+ description = open(os.path.join(here, "README.md")).read()
+except IOError:
+ description = None
+
+PACKAGE_VERSION = "2.0.1"
+
+deps = [
+ "mozinfo >= 0.7",
+ "mozfile >= 1.0",
+ "requests",
+ "six >= 1.13.0",
+]
+
+setup(
+ name="mozInstall",
+ version=PACKAGE_VERSION,
+ description="package for installing and uninstalling Mozilla applications",
+ long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
+ # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ classifiers=[
+ "Environment :: Console",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
+ "Natural Language :: English",
+ "Operating System :: OS Independent",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ ],
+ keywords="mozilla",
+ author="Mozilla Automation and Tools team",
+ author_email="tools@lists.mozilla.org",
+ url="https://wiki.mozilla.org/Auto-tools/Projects/Mozbase",
+ license="MPL 2.0",
+ packages=["mozinstall"],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=deps,
+ # we have to generate two more executables for those systems that cannot run as Administrator
+ # and the filename containing "install" triggers the UAC
+ entry_points="""
+ # -*- Entry points: -*-
+ [console_scripts]
+ mozinstall = mozinstall:install_cli
+ mozuninstall = mozinstall:uninstall_cli
+ moz_add_to_system = mozinstall:install_cli
+ moz_remove_from_system = mozinstall:uninstall_cli
+ """,
+)
diff --git a/testing/mozbase/mozinstall/tests/conftest.py b/testing/mozbase/mozinstall/tests/conftest.py
new file mode 100644
index 0000000000..132547a96b
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/conftest.py
@@ -0,0 +1,14 @@
+import pytest
+
+
+@pytest.fixture
+def get_installer(request):
+ def _get_installer(extension):
+ """Get path to the installer for the specified extension."""
+ stub_dir = request.node.fspath.dirpath("installer_stubs")
+
+ # We had to remove firefox.exe since it is not valid for mozinstall 1.12 and higher
+ # Bug 1157352 - We should grab a firefox.exe from the build process or download it
+ return stub_dir.join("firefox.{}".format(extension)).strpath
+
+ return _get_installer
diff --git a/testing/mozbase/mozinstall/tests/installer_stubs/firefox.dmg b/testing/mozbase/mozinstall/tests/installer_stubs/firefox.dmg
new file mode 100644
index 0000000000..dd9c779dfa
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/installer_stubs/firefox.dmg
Binary files differ
diff --git a/testing/mozbase/mozinstall/tests/installer_stubs/firefox.tar.bz2 b/testing/mozbase/mozinstall/tests/installer_stubs/firefox.tar.bz2
new file mode 100644
index 0000000000..cb046a0e7f
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/installer_stubs/firefox.tar.bz2
Binary files differ
diff --git a/testing/mozbase/mozinstall/tests/installer_stubs/firefox.zip b/testing/mozbase/mozinstall/tests/installer_stubs/firefox.zip
new file mode 100644
index 0000000000..7c3f61a5e9
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/installer_stubs/firefox.zip
Binary files differ
diff --git a/testing/mozbase/mozinstall/tests/manifest.ini b/testing/mozbase/mozinstall/tests/manifest.ini
new file mode 100644
index 0000000000..d5e0d06976
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/manifest.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+subsuite = mozbase
+[test_binary.py]
+skip-if = os == 'mac'
+[test_install.py]
+skip-if = os == 'mac' # intermittent
+[test_is_installer.py]
+[test_uninstall.py]
diff --git a/testing/mozbase/mozinstall/tests/test_binary.py b/testing/mozbase/mozinstall/tests/test_binary.py
new file mode 100644
index 0000000000..6454c78ef5
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/test_binary.py
@@ -0,0 +1,50 @@
+import os
+
+import mozinfo
+import mozinstall
+import mozunit
+import pytest
+
+
+@pytest.mark.skipif(
+ mozinfo.isWin,
+ reason="Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.",
+)
+def test_get_binary(tmpdir, get_installer):
+ """Test to retrieve binary from install path."""
+ if mozinfo.isLinux:
+ installdir = mozinstall.install(get_installer("tar.bz2"), tmpdir.strpath)
+ binary = os.path.join(installdir, "firefox")
+
+ assert mozinstall.get_binary(installdir, "firefox") == binary
+
+ elif mozinfo.isWin:
+ installdir_exe = mozinstall.install(
+ get_installer("exe"), tmpdir.join("exe").strpath
+ )
+ binary_exe = os.path.join(installdir_exe, "core", "firefox.exe")
+
+ assert mozinstall.get_binary(installdir_exe, "firefox") == binary_exe
+
+ installdir_zip = mozinstall.install(
+ get_installer("zip"), tmpdir.join("zip").strpath
+ )
+ binary_zip = os.path.join(installdir_zip, "firefox.exe")
+
+ assert mozinstall.get_binary(installdir_zip, "firefox") == binary_zip
+
+ elif mozinfo.isMac:
+ installdir = mozinstall.install(get_installer("dmg"), tmpdir.strpath)
+ binary = os.path.join(installdir, "Contents", "MacOS", "firefox")
+
+ assert mozinstall.get_binary(installdir, "firefox") == binary
+
+
+def test_get_binary_error(tmpdir):
+ """Test that an InvalidBinary error is raised."""
+ with pytest.raises(mozinstall.InvalidBinary):
+ mozinstall.get_binary(tmpdir.strpath, "firefox")
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/testing/mozbase/mozinstall/tests/test_install.py b/testing/mozbase/mozinstall/tests/test_install.py
new file mode 100644
index 0000000000..2dceb2cc78
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/test_install.py
@@ -0,0 +1,90 @@
+import subprocess
+
+import mozinfo
+import mozinstall
+import mozunit
+import pytest
+
+
+@pytest.mark.skipif(
+ mozinfo.isWin,
+ reason="Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.",
+)
+def test_is_installer(request, get_installer):
+ """Test that we can identify a correct installer."""
+ if mozinfo.isLinux:
+ assert mozinstall.is_installer(get_installer("tar.bz2"))
+
+ if mozinfo.isWin:
+ # test zip installer
+ assert mozinstall.is_installer(get_installer("zip"))
+
+ # test exe installer
+ assert mozinstall.is_installer(get_installer("exe"))
+
+ try:
+ # test stub browser file
+ # without pefile on the system this test will fail
+ import pefile # noqa
+
+ stub_exe = (
+ request.node.fspath.dirpath("build_stub").join("firefox.exe").strpath
+ )
+ assert not mozinstall.is_installer(stub_exe)
+ except ImportError:
+ pass
+
+ if mozinfo.isMac:
+ assert mozinstall.is_installer(get_installer("dmg"))
+
+
+def test_invalid_source_error(get_installer):
+ """Test that InvalidSource error is raised with an incorrect installer."""
+ if mozinfo.isLinux:
+ with pytest.raises(mozinstall.InvalidSource):
+ mozinstall.install(get_installer("dmg"), "firefox")
+
+ elif mozinfo.isWin:
+ with pytest.raises(mozinstall.InvalidSource):
+ mozinstall.install(get_installer("tar.bz2"), "firefox")
+
+ elif mozinfo.isMac:
+ with pytest.raises(mozinstall.InvalidSource):
+ mozinstall.install(get_installer("tar.bz2"), "firefox")
+
+ # Test an invalid url handler
+ with pytest.raises(mozinstall.InvalidSource):
+ mozinstall.install("file://foo.bar", "firefox")
+
+
+@pytest.mark.skipif(
+ mozinfo.isWin,
+ reason="Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.",
+)
+def test_install(tmpdir, get_installer):
+ """Test to install an installer."""
+ if mozinfo.isLinux:
+ installdir = mozinstall.install(get_installer("tar.bz2"), tmpdir.strpath)
+ assert installdir == tmpdir.join("firefox").strpath
+
+ elif mozinfo.isWin:
+ installdir_exe = mozinstall.install(
+ get_installer("exe"), tmpdir.join("exe").strpath
+ )
+ assert installdir_exe == tmpdir.join("exe", "firefox").strpath
+
+ installdir_zip = mozinstall.install(
+ get_installer("zip"), tmpdir.join("zip").strpath
+ )
+ assert installdir_zip == tmpdir.join("zip", "firefox").strpath
+
+ elif mozinfo.isMac:
+ installdir = mozinstall.install(get_installer("dmg"), tmpdir.strpath)
+ assert installdir == tmpdir.realpath().join("Firefox Stub.app").strpath
+
+ mounted_images = subprocess.check_output(["hdiutil", "info"]).decode("ascii")
+ assert get_installer("dmg") not in mounted_images
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/testing/mozbase/mozinstall/tests/test_is_installer.py b/testing/mozbase/mozinstall/tests/test_is_installer.py
new file mode 100644
index 0000000000..057c29f968
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/test_is_installer.py
@@ -0,0 +1,40 @@
+import mozinfo
+import mozinstall
+import mozunit
+import pytest
+
+
+@pytest.mark.skipif(
+ mozinfo.isWin,
+ reason="Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.",
+)
+def test_is_installer(request, get_installer):
+ """Test that we can identify a correct installer."""
+ if mozinfo.isLinux:
+ assert mozinstall.is_installer(get_installer("tar.bz2"))
+
+ if mozinfo.isWin:
+ # test zip installer
+ assert mozinstall.is_installer(get_installer("zip"))
+
+ # test exe installer
+ assert mozinstall.is_installer(get_installer("exe"))
+
+ try:
+ # test stub browser file
+ # without pefile on the system this test will fail
+ import pefile # noqa
+
+ stub_exe = (
+ request.node.fspath.dirpath("build_stub").join("firefox.exe").strpath
+ )
+ assert not mozinstall.is_installer(stub_exe)
+ except ImportError:
+ pass
+
+ if mozinfo.isMac:
+ assert mozinstall.is_installer(get_installer("dmg"))
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/testing/mozbase/mozinstall/tests/test_uninstall.py b/testing/mozbase/mozinstall/tests/test_uninstall.py
new file mode 100644
index 0000000000..45298a834d
--- /dev/null
+++ b/testing/mozbase/mozinstall/tests/test_uninstall.py
@@ -0,0 +1,39 @@
+import mozinfo
+import mozinstall
+import mozunit
+import py
+import pytest
+
+
+@pytest.mark.skipif(
+ mozinfo.isWin,
+ reason="Bug 1157352 - New firefox.exe needed for mozinstall 1.12 and higher.",
+)
+def test_uninstall(tmpdir, get_installer):
+ """Test to uninstall an installed binary."""
+ if mozinfo.isLinux:
+ installdir = mozinstall.install(get_installer("tar.bz2"), tmpdir.strpath)
+ mozinstall.uninstall(installdir)
+ assert not py.path.local(installdir).check()
+
+ elif mozinfo.isWin:
+ installdir_exe = mozinstall.install(
+ get_installer("exe"), tmpdir.join("exe").strpath
+ )
+ mozinstall.uninstall(installdir_exe)
+ assert not py.path.local(installdir).check()
+
+ installdir_zip = mozinstall.install(
+ get_installer("zip"), tmpdir.join("zip").strpath
+ )
+ mozinstall.uninstall(installdir_zip)
+ assert not py.path.local(installdir).check()
+
+ elif mozinfo.isMac:
+ installdir = mozinstall.install(get_installer("dmg"), tmpdir.strpath)
+ mozinstall.uninstall(installdir)
+ assert not py.path.local(installdir).check()
+
+
+if __name__ == "__main__":
+ mozunit.main()