commit 90424972214a70dbb9007e6f32a7573b337efb57
parent a09f544f744b748285c8336368cf1a1d6cf62659
Author: Alexandre Lissy <lissyx+mozillians@lissyx.dyndns.org>
Date: Wed, 19 Nov 2025 17:46:18 +0000
Bug 259356 - Add integration testing of XDG handling via Marionette r=whimboo,mossop
Differential Revision: https://phabricator.services.mozilla.com/D213186
Diffstat:
10 files changed, 314 insertions(+), 0 deletions(-)
diff --git a/toolkit/xre/test/marionette/manifest.toml b/toolkit/xre/test/marionette/manifest.toml
@@ -1,5 +1,7 @@
[DEFAULT]
+["include:xdg_config/manifest.toml"]
+
["test_exitcode.py"]
["test_fission_autostart.py"]
diff --git a/toolkit/xre/test/marionette/xdg_config/manifest.toml b/toolkit/xre/test/marionette/xdg_config/manifest.toml
@@ -0,0 +1,19 @@
+[DEFAULT]
+
+["test_xdg_config_legacy_existing.py"]
+run-if = ["os == 'linux'"]
+
+["test_xdg_config_legacy_forced.py"]
+run-if = ["os == 'linux'"]
+
+["test_xdg_config_new.py"]
+run-if = ["os == 'linux'"]
+
+["test_xdg_config_new_env.py"]
+run-if = ["os == 'linux'"]
+
+["test_xdg_config_new_env_invalid.py"]
+run-if = ["os == 'linux'"]
+
+["test_xdg_config_new_existing.py"]
+run-if = ["os == 'linux'"]
diff --git a/toolkit/xre/test/marionette/xdg_config/test_xdg_config_legacy_existing.py b/toolkit/xre/test/marionette/xdg_config/test_xdg_config_legacy_existing.py
@@ -0,0 +1,32 @@
+import os
+import sys
+
+import mozfile
+
+sys.path.append(os.path.dirname(__file__))
+
+from xdg_config_home_test_case import XdgConfigHomeTestCase
+
+
+class TestXdgConfigHomeLegacyExisting(XdgConfigHomeTestCase):
+
+ def setUp(self):
+ assert "XDG_CONFIG_HOME" not in self._env.keys()
+ self._env.update({"XDG_CONFIG_HOME": f"{self.homedir}/.config-test/"})
+ self.product_root = self.make_product_root(os.path.join(".mozilla", "firefox"))
+ super().setUp()
+
+ def tearDown(self):
+ mozfile.remove(self.product_root)
+ assert not os.path.exists(self.product_root)
+ super().tearDown()
+
+ def test_profile_dir(self):
+ self.client.navigate(self.about_support)
+
+ profile_subdir = self.get_asserted_profile_subdir()
+ print(f"profile_subdir={profile_subdir}")
+ self.assertTrue(
+ profile_subdir.startswith(".mozilla/firefox"),
+ "Profile is under '.mozilla/firefox'",
+ )
diff --git a/toolkit/xre/test/marionette/xdg_config/test_xdg_config_legacy_forced.py b/toolkit/xre/test/marionette/xdg_config/test_xdg_config_legacy_forced.py
@@ -0,0 +1,23 @@
+import os
+import sys
+
+sys.path.append(os.path.dirname(__file__))
+
+from xdg_config_home_test_case import XdgConfigHomeTestCase
+
+
+class TestXdgConfigHomeLegacy(XdgConfigHomeTestCase):
+
+ def setUp(self):
+ assert "MOZ_LEGACY_HOME" not in self._env.keys()
+ self._env.update({"MOZ_LEGACY_HOME": "1"})
+ super().setUp()
+
+ def test_profile_dir(self):
+ self.client.navigate(self.about_support)
+
+ profile_subdir = self.get_asserted_profile_subdir()
+ self.assertTrue(
+ profile_subdir.startswith(".mozilla/firefox"),
+ "Profile is under .mozilla/firefox",
+ )
diff --git a/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new.py b/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new.py
@@ -0,0 +1,18 @@
+import os
+import sys
+
+sys.path.append(os.path.dirname(__file__))
+
+from xdg_config_home_test_case import XdgConfigHomeTestCase
+
+
+class TestXdgConfigHomeNew(XdgConfigHomeTestCase):
+
+ def test_profile_dir(self):
+ self.client.navigate(self.about_support)
+
+ profile_subdir = self.get_asserted_profile_subdir()
+ self.assertTrue(
+ profile_subdir.startswith(".config/mozilla/firefox"),
+ "Profile is under $HOME/.config/mozilla/firefox",
+ )
diff --git a/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new_env.py b/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new_env.py
@@ -0,0 +1,23 @@
+import os
+import sys
+
+sys.path.append(os.path.dirname(__file__))
+
+from xdg_config_home_test_case import XdgConfigHomeTestCase
+
+
+class TestXdgConfigHomeNewEnv(XdgConfigHomeTestCase):
+
+ def setUp(self):
+ assert "XDG_CONFIG_HOME" not in self._env.keys()
+ self._env.update({"XDG_CONFIG_HOME": f"{self.homedir}/mozXDG-config-dir"})
+ super().setUp()
+
+ def test_profile_dir(self):
+ self.client.navigate(self.about_support)
+
+ profile_subdir = self.get_asserted_profile_subdir()
+ self.assertTrue(
+ profile_subdir.startswith("mozXDG-config-dir/mozilla/firefox"),
+ "Profile is under 'mozXDG-config-dir/mozilla/firefox'",
+ )
diff --git a/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new_env_invalid.py b/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new_env_invalid.py
@@ -0,0 +1,23 @@
+import os
+import sys
+
+sys.path.append(os.path.dirname(__file__))
+
+from xdg_config_home_test_case import XdgConfigHomeTestCase
+
+
+class TestXdgConfigHomeNewEnvInvalid(XdgConfigHomeTestCase):
+
+ def setUp(self):
+ assert "XDG_CONFIG_HOME" not in self._env.keys()
+ self._env.update({"XDG_CONFIG_HOME": "$HOME/mozXDG-config-dir"})
+ super().setUp()
+
+ def test_profile_dir(self):
+ self.client.navigate(self.about_support)
+
+ profile_subdir = self.get_asserted_profile_subdir()
+ self.assertTrue(
+ profile_subdir.startswith(".config/mozilla/firefox"),
+ "Profile is under '.config/mozilla/firefox'",
+ )
diff --git a/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new_existing.py b/toolkit/xre/test/marionette/xdg_config/test_xdg_config_new_existing.py
@@ -0,0 +1,33 @@
+import os
+import sys
+
+import mozfile
+
+sys.path.append(os.path.dirname(__file__))
+
+from xdg_config_home_test_case import XdgConfigHomeTestCase
+
+
+class TestXdgConfigHomeNewExisting(XdgConfigHomeTestCase):
+
+ def setUp(self):
+ assert "XDG_CONFIG_HOME" not in self._env.keys()
+ self._env.update({"XDG_CONFIG_HOME": f"{self.homedir}/.config-test/"})
+ self.product_root = self.make_product_root(
+ os.path.join(".config-test", "mozilla", "firefox")
+ )
+ super().setUp()
+
+ def tearDown(self):
+ mozfile.remove(self.product_root)
+ assert not os.path.exists(self.product_root)
+ super().tearDown()
+
+ def test_profile_dir(self):
+ self.client.navigate(self.about_support)
+
+ profile_subdir = self.get_asserted_profile_subdir()
+ self.assertTrue(
+ profile_subdir.startswith(".config-test/mozilla/firefox"),
+ "Profile is under '.config-test/mozilla/firefox'",
+ )
diff --git a/toolkit/xre/test/marionette/xdg_config/xdg_config_home_test_case.py b/toolkit/xre/test/marionette/xdg_config/xdg_config_home_test_case.py
@@ -0,0 +1,140 @@
+import os
+import subprocess
+import tempfile
+
+import mozfile
+from marionette_driver.marionette import Marionette
+from marionette_harness import MarionetteTestCase
+
+
+class XdgConfigHomeTestCase(MarionetteTestCase):
+ about_support = "about:support"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._created_dirs = []
+
+ self.tmproot = tempfile.mkdtemp(prefix="mozrunner-xdg_config-test")
+
+ self.bin = None
+ self.tmphome = os.path.join(self.tmproot, "DEFAULT")
+ self.homedir = self.get_home_root()
+
+ self._env = os.environ.copy()
+ self._env.update({"HOME": self.homedir})
+
+ self.process_handler = None
+
+ def setUp(self):
+ super().setUp()
+
+ if not self.bin:
+ self.bin = self.marionette.instance.binary
+
+ self._cmd = [
+ self.bin,
+ "--headless",
+ "-marionette",
+ "-remote-allow-system-access",
+ ]
+
+ self.marionette.quit(in_app=False)
+ self.client = Marionette(host="127.0.0.1", port=2828)
+
+ self.start()
+ self.client.start_session()
+
+ def tearDown(self):
+ self.process_handler.kill()
+ self.process_handler.wait()
+ self.process_handler = None
+
+ self.client = None
+
+ super().tearDown()
+
+ self.cleanup()
+
+ def start(self):
+ self.assert_safe_homedir()
+ _env = self._env.copy()
+ self.process_handler = subprocess.Popen(self._cmd, env=self._env)
+
+ def assert_safe_homedir(self):
+ assert (
+ "mozrunner-xdg_config-test" in self.homedir
+ ), f"HOME is not real user's home: {self.homedir}"
+
+ def get_home_root(self):
+ rv = tempfile.mkdtemp(prefix="{}.".format("run"), dir=self.tmproot)
+ self._created_dirs.append(rv)
+ return rv
+
+ def make_product_root(self, subpath):
+ product_root = os.path.join(self.homedir, subpath)
+ assert not os.path.exists(product_root), f"no {product_root}"
+ os.makedirs(product_root)
+
+ profiles_ini_path = os.path.join(product_root, "profiles.ini")
+ assert not os.path.exists(profiles_ini_path)
+ with open(profiles_ini_path, "w") as profiles_ini:
+ profiles_ini.write(
+ """
+[General]
+StartWithLastProfile=1
+Version=2
+"""
+ )
+ return product_root
+
+ def find_one_existing_test_run_directory(self):
+ dirs = os.listdir(self.tmproot)
+ test_dir = list(filter(lambda e: e.startswith("run."), dirs))
+ assert len(test_dir) == 1
+ return os.path.join(self.tmproot, test_dir[0])
+
+ def find_one_profile_run_dir(self):
+ test_dir_walk = list(os.walk(self.homedir))
+ maybe_profile = list(
+ filter(lambda e: "compatibility.ini" in e[2], test_dir_walk)
+ )
+ assert len(maybe_profile) == 1
+ (profile_dir, _, _) = maybe_profile[0]
+ return profile_dir
+
+ def cleanup(self):
+ for d in self._created_dirs:
+ mozfile.remove(d)
+ assert not os.path.exists(d)
+
+ if self.tmproot:
+ mozfile.remove(self.tmproot)
+ assert not os.path.exists(self.tmproot)
+
+ def get_process_env_value(self, name):
+ with self.client.using_context(self.client.CONTEXT_CHROME):
+ rv = self.client.execute_script(
+ f"""
+ return Services.env.get("{name}");
+ """
+ )
+ return rv
+
+ def get_profile_dir(self):
+ with self.client.using_context(self.client.CONTEXT_CHROME):
+ rv = self.client.execute_script(
+ """
+ return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
+ """
+ )
+ return rv
+
+ def get_asserted_profile_subdir(self):
+ profile_dir = self.get_profile_dir()
+ common = os.path.commonpath((self.homedir, profile_dir))
+ self.assertTrue(
+ len(common) > 0,
+ f"Profile dir {profile_dir} is a subdir of homedir: {self.homedir}",
+ )
+
+ return os.path.relpath(profile_dir, self.homedir)
diff --git a/tools/lint/dot-mozilla-reference.yml b/tools/lint/dot-mozilla-reference.yml
@@ -39,5 +39,6 @@ avoid-dot-mozilla-without-xdg:
- toolkit/crashreporter/crash_helper_server/src/logging/env.rs
- toolkit/moz.configure
- toolkit/xre/nsXREDirProvider.cpp
+ - toolkit/xre/test/marionette/xdg_config/test_xdg_config_legacy_existing.py
- toolkit/tests/gtest/TestXREAppDir.cpp
- xpcom/io/nsAppFileLocationProvider.cpp