From d0dbe23663f33cbd3c1a0463fcfb06db628c7aa7 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:01:12 -0800 Subject: [PATCH 01/18] gamelauncher.py: update parse_args - Reimplement parse_args to simply return a single positional argument string or a Namespace object. As a result the new usage will be: $ gamelauncher.py /home/foo/foo.exe $ gamelauncher.py /home/foo/foo.exe -opengl ... $ gamelauncher.py --config example.toml - Everything following the program will be interpreted as a single positional argument and this removes the need for quotes for the envvar usage. The optional arguments: --exe, --options, --store and --verb will no longer be supported. --- gamelauncher.py | 62 ++++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/gamelauncher.py b/gamelauncher.py index 49fc9da11..279371c03 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -2,11 +2,11 @@ import os import argparse -from argparse import ArgumentParser, _ArgumentGroup, Namespace +from argparse import ArgumentParser, Namespace import sys from pathlib import Path import tomllib -from typing import Dict, Any, List, Set +from typing import Dict, Any, List, Set, Union import gamelauncher_plugins from re import match @@ -14,57 +14,35 @@ import subprocess -def parse_args() -> Namespace: # noqa: D103 - stores: List[str] = [ - "amazon", - "battlenet", - "ea", - "egs", - "gog", - "humble", - "itchio", - "ubisoft", - ] +def parse_args() -> Union[Namespace, str]: # noqa: D103 + opt_args: Set[str] = {"--help", "-h", "--config"} exe: str = Path(__file__).name - usage: str = """ + usage: str = f""" example usage: - {} --config example.toml - {} --config /home/foo/example.toml --options '-opengl' - WINEPREFIX= GAMEID= PROTONPATH= {} --exe /home/foo/example.exe --options '-opengl' - WINEPREFIX= GAMEID= PROTONPATH= {} --exe /home/foo/example.exe --store gog - WINEPREFIX= GAMEID= PROTONPATH= {} --exe "" - WINEPREFIX= GAMEID= PROTONPATH= {} --exe /home/foo/example.exe --verb waitforexitandrun - """.format(exe, exe, exe, exe, exe, exe) - + WINEPREFIX= GAMEID= PROTONPATH= {exe} /home/foo/example.exe + WINEPREFIX= GAMEID= PROTONPATH= {exe} /home/foo/example.exe -opengl + WINEPREFIX= GAMEID= PROTONPATH= {exe} "" + WINEPREFIX= GAMEID= PROTONPATH= PROTON_VERB= {exe} /home/foo/example.exe + WINEPREFIX= GAMEID= PROTONPATH= STORE= {exe} /home/foo/example.exe + {exe} --config /home/foo/example.toml + """ parser: ArgumentParser = argparse.ArgumentParser( description="Unified Linux Wine Game Launcher", epilog=usage, formatter_class=argparse.RawTextHelpFormatter, ) - group: _ArgumentGroup = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--config", help="path to TOML file") - group.add_argument( - "--exe", - help="path to game executable\npass an empty string to create a prefix", - default=None, - ) - parser.add_argument( - "--verb", - help="a verb to pass to Proton (default: waitforexitandrun)", - ) - parser.add_argument( - "--options", - help="launch options for game executable\nNOTE: options must be wrapped in quotes", - ) - parser.add_argument( - "--store", - help=f"the store of the game executable\nNOTE: will override the store specified in config\nexamples: {stores}", - ) + parser.add_argument("--config", help="path to TOML file") - return parser.parse_args(sys.argv[1:]) + if not sys.argv[1:]: + err: str = "Please see project README.md for more info and examples.\nhttps://github.com/Open-Wine-Components/ULWGL-launcher" + parser.print_help() + raise SystemExit(err) + if sys.argv[1:][0] in opt_args: + return parser.parse_args(sys.argv[1:]) def _setup_pfx(path: str) -> None: + return sys.argv[1:][0] """Create a symlink to the WINE prefix and tracked_files file.""" if not (Path(path + "/pfx")).expanduser().is_symlink(): # When creating the symlink, we want it to be in expanded form when passed unexpanded paths From 9642748cb09f62fea8f1eb4dc15df39a12c65231 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:03:45 -0800 Subject: [PATCH 02/18] gamelauncher.py: move setup_pfx to main --- gamelauncher.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gamelauncher.py b/gamelauncher.py index 279371c03..efe9b39b0 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -41,8 +41,10 @@ def parse_args() -> Union[Namespace, str]: # noqa: D103 if sys.argv[1:][0] in opt_args: return parser.parse_args(sys.argv[1:]) -def _setup_pfx(path: str) -> None: return sys.argv[1:][0] + + +def setup_pfx(path: str) -> None: """Create a symlink to the WINE prefix and tracked_files file.""" if not (Path(path + "/pfx")).expanduser().is_symlink(): # When creating the symlink, we want it to be in expanded form when passed unexpanded paths @@ -87,7 +89,6 @@ def set_env(env: Dict[str, str], args: Namespace) -> Dict[str, str]: Expects to be invoked if not reading a TOML file """ - _setup_pfx(env["WINEPREFIX"]) is_create_prefix: bool = False if not getattr(args, "exe", None): @@ -143,7 +144,6 @@ def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]: raise ValueError(err) if key == "prefix": env["WINEPREFIX"] = val - _setup_pfx(val) elif key == "game_id": env["GAMEID"] = val elif key == "proton": @@ -281,6 +281,7 @@ def main() -> None: # noqa: D103 env["EXE"] = "" env["STEAM_COMPAT_INSTALL_PATH"] = "" verb = "waitforexitandrun" + setup_pfx(env["WINEPREFIX"]) # Game Drive functionality gamelauncher_plugins.enable_steam_game_drive(env) From bba9f0d06009b327528e8f7282625e4d7657d114 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:09:18 -0800 Subject: [PATCH 03/18] gamelauncher.py: refactor main - Move environment variable functionality to set_env and execute it for both usages. --- gamelauncher.py | 110 ++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/gamelauncher.py b/gamelauncher.py index efe9b39b0..a1b02cb99 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -84,24 +84,62 @@ def check_env(env: Dict[str, str]) -> Dict[str, str]: return env -def set_env(env: Dict[str, str], args: Namespace) -> Dict[str, str]: +def set_env(env: Dict[str, str], args: Union[Namespace, str]) -> Dict[str, str]: """Set various environment variables for the Steam RT. - Expects to be invoked if not reading a TOML file + Filesystem paths will be formatted and expanded as POSIX """ - is_create_prefix: bool = False + verbs: Set[str] = { + "waitforexitandrun", + "run", + "runinprefix", + "destroyprefix", + "getcompatpath", + "getnativepath", + } + + # PROTON_VERB + # For invalid Proton verbs, just assign the waitforexitandrun + if "PROTON_VERB" in os.environ and os.environ["PROTON_VERB"] in verbs: + env["PROTON_VERB"] = os.environ["PROTON_VERB"] + else: + env["PROTON_VERB"] = "waitforexitandrun" + + # EXE + # Empty string for EXE will be used to create a prefix + if isinstance(args, str) and not args: + env["EXE"] = "" + env["STEAM_COMPAT_INSTALL_PATH"] = "" + env["PROTON_VERB"] = "waitforexitandrun" + elif isinstance(args, str): + env["EXE"] = Path(args).expanduser().as_posix() + env["STEAM_COMPAT_INSTALL_PATH"] = Path(env["EXE"]).parent.as_posix() + else: + # Config branch + env["EXE"] = Path(env["EXE"]).expanduser().as_posix() + env["STEAM_COMPAT_INSTALL_PATH"] = Path(env["EXE"]).parent.as_posix() + + if "STORE" in os.environ: + env["STORE"] = os.environ["STORE"] - if not getattr(args, "exe", None): - is_create_prefix = True + # ULWGL_ID + env["ULWGL_ID"] = env["GAMEID"] + env["STEAM_COMPAT_APP_ID"] = "0" + + if match(r"^ulwgl-[\d\w]+$", env["ULWGL_ID"]): + env["STEAM_COMPAT_APP_ID"] = env["ULWGL_ID"][env["ULWGL_ID"].find("-") + 1 :] + env["SteamAppId"] = env["STEAM_COMPAT_APP_ID"] + env["SteamGameId"] = env["SteamAppId"] - # Sets the environment variables: EXE - for arg, val in vars(args).items(): - if arg == "exe" and not is_create_prefix: - # NOTE: options can possibly be appended at the end - env["EXE"] = val - elif arg == "options" and val and not is_create_prefix: - # NOTE: assume it's space separated - env["EXE"] = env["EXE"] + " " + " ".join(val.split(" ")) + # PATHS + env["WINEPREFIX"] = Path(env["WINEPREFIX"]).expanduser().as_posix() + env["PROTONPATH"] = Path(env["PROTONPATH"]).expanduser().as_posix() + env["STEAM_COMPAT_DATA_PATH"] = env["WINEPREFIX"] + env["STEAM_COMPAT_SHADER_PATH"] = env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" + env["STEAM_COMPAT_TOOL_PATHS"] = ( + env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() + ) + env["STEAM_COMPAT_MOUNTS"] = env["STEAM_COMPAT_TOOL_PATHS"] return env @@ -231,57 +269,19 @@ def main() -> None: # noqa: D103 "SteamGameId": "", "STEAM_RUNTIME_LIBRARY_PATH": "", "STORE": "", + "PROTON_VERB": "", } command: List[str] = [] - verb: str = "waitforexitandrun" # Represents a valid list of current supported Proton verbs - verbs: Set[str] = { - "waitforexitandrun", - "run", - "runinprefix", - "destroyprefix", - "getcompatpath", - "getnativepath", - } - args: Namespace = parse_args() + args: Union[Namespace, str] = parse_args() - if getattr(args, "config", None): + if isinstance(args, Namespace): set_env_toml(env, args) else: check_env(env) - set_env(env, args) - if getattr(args, "verb", None) and getattr(args, "verb", None) in verbs: - verb = getattr(args, "verb", None) - - if getattr(args, "store", None): - env["STORE"] = getattr(args, "store", None) - - env["ULWGL_ID"] = env["GAMEID"] - env["STEAM_COMPAT_APP_ID"] = "0" - - if match(r"^ulwgl-[\d\w]+$", env["ULWGL_ID"]): - env["STEAM_COMPAT_APP_ID"] = env["ULWGL_ID"][env["ULWGL_ID"].find("-") + 1 :] - - env["SteamAppId"] = env["STEAM_COMPAT_APP_ID"] - env["SteamGameId"] = env["SteamAppId"] - env["WINEPREFIX"] = Path(env["WINEPREFIX"]).expanduser().as_posix() - env["PROTONPATH"] = Path(env["PROTONPATH"]).expanduser().as_posix() - env["STEAM_COMPAT_DATA_PATH"] = env["WINEPREFIX"] - env["STEAM_COMPAT_SHADER_PATH"] = env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" - env["STEAM_COMPAT_INSTALL_PATH"] = Path(env["EXE"]).parent.expanduser().as_posix() - env["EXE"] = Path(env["EXE"]).expanduser().as_posix() - env["STEAM_COMPAT_TOOL_PATHS"] = ( - env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() - ) - env["STEAM_COMPAT_MOUNTS"] = env["STEAM_COMPAT_TOOL_PATHS"] - - # Create an empty Proton prefix when asked - if not getattr(args, "exe", None) and not getattr(args, "config", None): - env["EXE"] = "" - env["STEAM_COMPAT_INSTALL_PATH"] = "" - verb = "waitforexitandrun" setup_pfx(env["WINEPREFIX"]) + set_env(env, args) # Game Drive functionality gamelauncher_plugins.enable_steam_game_drive(env) From 9b0402074c3c54b68b1e0b7cb15db347e0d454c2 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:09:59 -0800 Subject: [PATCH 04/18] gamelauncher.py: add PROTON_VERB to build_command --- gamelauncher.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gamelauncher.py b/gamelauncher.py index a1b02cb99..78e79844e 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -214,13 +214,14 @@ def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]: return env -def build_command(env: Dict[str, str], command: List[str], verb: str) -> List[str]: +def build_command(env: Dict[str, str], command: List[str]) -> List[str]: """Build the command to be executed.""" paths: List[Path] = [ Path(Path().home().as_posix() + "/.local/share/ULWGL/ULWGL"), Path(Path(__file__).cwd().as_posix() + "/ULWGL"), ] entry_point: str = "" + verb: str = env["PROTON_VERB"] # Find the ULWGL script in $HOME/.local/share then cwd for path in paths: @@ -292,7 +293,7 @@ def main() -> None: # noqa: D103 print(f"Setting environment variable: {key}={val}") os.environ[key] = val - build_command(env, command, verb) + build_command(env, command) print(f"The following command will be executed: {command}") subprocess.run(command, check=True, stdout=subprocess.PIPE, text=True) From db6c81eacbe05f9a70766d5010ed035fe73683a9 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:10:18 -0800 Subject: [PATCH 05/18] gamelauncher.py: update comments --- gamelauncher.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gamelauncher.py b/gamelauncher.py index 78e79844e..164e1199a 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -9,8 +9,6 @@ from typing import Dict, Any, List, Set, Union import gamelauncher_plugins from re import match - -# TODO: Only set the environment variables that are not empty import subprocess @@ -284,10 +282,10 @@ def main() -> None: # noqa: D103 setup_pfx(env["WINEPREFIX"]) set_env(env, args) - # Game Drive functionality + # Game Drive gamelauncher_plugins.enable_steam_game_drive(env) - # Set all environment variable + # Set all environment variables # NOTE: `env` after this block should be read only for key, val in env.items(): print(f"Setting environment variable: {key}={val}") From 0712a21d482288224212001a0c56dd24f5abd8da Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sun, 11 Feb 2024 09:58:40 -0800 Subject: [PATCH 06/18] gamelauncher.py: move config checks to check_env --- gamelauncher.py | 75 ++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/gamelauncher.py b/gamelauncher.py index 164e1199a..ba55860e7 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -52,11 +52,52 @@ def setup_pfx(path: str) -> None: Path(path + "/tracked_files").expanduser().touch() -def check_env(env: Dict[str, str]) -> Dict[str, str]: +def check_env( + env: Dict[str, str], toml: Dict[str, Any] = None +) -> Union[Dict[str, str], Dict[str, Any]]: """Before executing a game, check for environment variables and set them. WINEPREFIX, GAMEID and PROTONPATH are strictly required. """ + if toml: + # Check for required or empty key/value pairs when reading a TOML config + # NOTE: Casing matters in the config and we don't check if the game id is set + table: str = "ulwgl" + required_keys: List[str] = ["proton", "prefix", "exe"] + + if table not in toml: + err: str = f"Table '{table}' in TOML is not defined." + raise ValueError(err) + + for key in required_keys: + if key not in toml[table]: + err: str = f"The following key in table '{table}' is required: {key}" + raise ValueError(err) + + # Raise an error for executables that do not exist + # One case this can happen is when game options are appended at the end of the exe + # Users should use launch_args for that + if key == "exe" and not Path(toml[table][key]).expanduser().is_file(): + val: str = toml[table][key] + err: str = f"Value for key '{key}' in TOML is not a file: {val}" + raise FileNotFoundError(err) + + # The proton and wine prefix need to be folders + if ( + key == "proton" and not Path(toml[table][key]).expanduser().is_dir() + ) or (key == "prefix" and not Path(toml[table][key]).expanduser().is_dir()): + dir: str = Path(toml[table][key]).expanduser().as_posix() + err: str = f"Value for key '{key}' in TOML is not a directory: {dir}" + raise NotADirectoryError(err) + + # Check for empty keys + for key, val in toml[table].items(): + if not val and isinstance(val, str): + err: str = f"Value is empty for '{key}' in TOML.\nPlease specify a value or remove the following entry:\n{key} = {val}" + raise ValueError(err) + + return toml + if "WINEPREFIX" not in os.environ: err: str = "Environment variable not set or not a directory: WINEPREFIX" raise ValueError(err) @@ -77,7 +118,6 @@ def check_env(env: Dict[str, str]) -> Dict[str, str]: err: str = "Environment variable not set or not a directory: PROTONPATH" raise ValueError(err) env["PROTONPATH"] = os.environ["PROTONPATH"] - env["STEAM_COMPAT_INSTALL_PATH"] = os.environ["PROTONPATH"] return env @@ -164,51 +204,22 @@ def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]: with Path(path_config).open(mode="rb") as file: toml = tomllib.load(file) - if not ( - Path(toml["ulwgl"]["prefix"]).expanduser().is_dir() - or Path(toml["ulwgl"]["proton"]).expanduser().is_dir() - ): - err: str = "Value for 'prefix' or 'proton' in TOML is not a directory." - raise NotADirectoryError(err) + check_env(env, toml) - # Set the values read from TOML to environment variables - # If necessary, raise an error on invalid inputs for key, val in toml["ulwgl"].items(): - # Handle cases for empty values - if not val and isinstance(val, str): - err: str = f'Value is empty for key in TOML: {key}\nPlease specify a value or remove the following entry:\n{key} = "{val}"' - raise ValueError(err) if key == "prefix": env["WINEPREFIX"] = val elif key == "game_id": env["GAMEID"] = val elif key == "proton": env["PROTONPATH"] = val - env["STEAM_COMPAT_INSTALL_PATH"] = val elif key == "store": env["STORE"] = val elif key == "exe": - # Raise an error for executables that do not exist - # One case this can happen is when game options are appended at the end of the exe - if not Path(val).expanduser().is_file(): - err: str = "Value for key 'exe' in TOML is not a file." - raise FileNotFoundError(err) - - # It's possible for users to pass values to --options - # Add any if they exist if toml.get("ulwgl").get("launch_args"): env["EXE"] = val + " " + " ".join(toml.get("ulwgl").get("launch_args")) else: env["EXE"] = val - - if getattr(args, "options", None): - # Assume space separated options and just trust it - env["EXE"] = ( - env["EXE"] - + " " - + " ".join(getattr(args, "options", None).split(" ")) - ) - return env From 490ecd1bb05e1fd6d156b8f31acb994cfc3f6357 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sun, 11 Feb 2024 09:59:14 -0800 Subject: [PATCH 07/18] gamelauncher.py: define return type to game drive --- gamelauncher_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamelauncher_plugins.py b/gamelauncher_plugins.py index 4c176e85f..f8c33bb1b 100644 --- a/gamelauncher_plugins.py +++ b/gamelauncher_plugins.py @@ -3,7 +3,7 @@ from typing import Dict, Set -def enable_steam_game_drive(env: Dict[str, str]): +def enable_steam_game_drive(env: Dict[str, str]) -> Dict[str, str]: """Enable Steam Game Drive functionality. Expects STEAM_COMPAT_INSTALL_PATH to be set From 86f04a939e9ebcdef02ba9f48123b4424056a89e Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sun, 11 Feb 2024 10:28:49 -0800 Subject: [PATCH 08/18] gamelauncher_test.py: update tests for new usage --- gamelauncher_test.py | 782 ++++++++----------------------------------- 1 file changed, 136 insertions(+), 646 deletions(-) diff --git a/gamelauncher_test.py b/gamelauncher_test.py index 0589157ab..45552222b 100644 --- a/gamelauncher_test.py +++ b/gamelauncher_test.py @@ -38,6 +38,8 @@ def setUp(self): "SteamGameId": "", "STEAM_RUNTIME_LIBRARY_PATH": "", "ULWGL_ID": "", + "STORE": "", + "PROTON_VERB": "", } self.test_opts = "-foo -bar" # Proton verb @@ -66,85 +68,32 @@ def test_game_drive_empty(self): During this process, we attempt to prepare setting up game drive and set the values for STEAM_RUNTIME_LIBRARY_PATH and STEAM_COMPAT_INSTALL_PATHS The resulting value of those variables should be colon delimited string with no leading colons and contain only /usr/lib or /usr/lib32 """ - result = None - result_set_env = None - result_check_env = None + args = None result_gamedrive = None Path(self.test_file + "/proton").touch() # Replicate main's execution and test up until enable_steam_game_drive - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=""), - ): + with patch("sys.argv", ["", ""]): os.environ["WINEPREFIX"] = self.test_file os.environ["PROTONPATH"] = self.test_file os.environ["GAMEID"] = self.test_file - # Parse arguments - result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - # Check if required env var are set - result_check_env = gamelauncher.check_env(self.env) - self.assertTrue(result_check_env is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) - self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" - ) - - # Set the required environment variables - result_set_env = gamelauncher.set_env(self.env, result) - self.assertTrue(result_set_env is self.env, "Expected the same reference") - - # Check for expected changes - # We only check the required ones - self.assertEqual(result_set_env["WINEPREFIX"], self.test_file) - self.assertEqual(result_set_env["PROTONPATH"], self.test_file) - self.assertEqual(result_set_env["GAMEID"], self.test_file) - # Check if the EXE is empty - self.assertFalse(result_set_env["EXE"], "Expected EXE to be empty") - - self.env["ULWGL_ID"] = self.env["GAMEID"] - self.env["STEAM_COMPAT_APP_ID"] = "0" - - if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): - self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ - self.env["ULWGL_ID"].find("-") + 1 : - ] - - self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] - self.env["SteamGameId"] = self.env["SteamAppId"] - self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() - self.env["PROTONPATH"] = Path(self.env["PROTONPATH"]).expanduser().as_posix() - self.env["STEAM_COMPAT_DATA_PATH"] = self.env["WINEPREFIX"] - self.env["STEAM_COMPAT_SHADER_PATH"] = ( - self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" - ) - self.env["STEAM_COMPAT_INSTALL_PATH"] = ( - Path(self.env["EXE"]).parent.expanduser().as_posix() - ) - self.env["EXE"] = Path(self.env["EXE"]).expanduser().as_posix() - self.env["STEAM_COMPAT_TOOL_PATHS"] = ( - self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() - ) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] + os.environ["STORE"] = self.test_file + # Args + args = gamelauncher.parse_args() + # Config + gamelauncher.check_env(self.env) + # Prefix + gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + # Env + gamelauncher.set_env(self.env, args) + # Game drive + result_gamedrive = gamelauncher_plugins.enable_steam_game_drive(self.env) - if not getattr(result, "exe", None) and not getattr(result, "config", None): - self.env["EXE"] = "" - self.env["STEAM_COMPAT_INSTALL_PATH"] = "" - self.verb = "waitforexitandrun" + for key, val in self.env.items(): + os.environ[key] = val - # Game Drive - result_gamedrive = gamelauncher_plugins.enable_steam_game_drive(self.env) + # Game drive self.assertTrue(result_gamedrive is self.env, "Expected the same reference") - self.assertTrue( self.env["STEAM_RUNTIME_LIBRARY_PATH"], "Expected two elements in STEAM_RUNTIME_LIBRARY_PATHS", @@ -171,105 +120,11 @@ def test_game_drive_empty(self): ) self.assertFalse(self.env["EXE"], "Expected EXE to be empty on empty string") - def test_build_command_verb(self): - """Test build_command. - - An error should not be raised if we pass a Proton verb we don't expect - By default, we use "waitforexitandrun" for a verb we don't expect - Currently we only expect: - "waitforexitandrun" - "run" - "runinprefix" - "destroyprefix" - "getcompatpath" - "getnativepath" - """ - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] - exe = "{self.test_exe}" - """ - toml_path = self.test_file + "/" + test_toml - result = None - result_set_env = None - test_command = [] - test_verb = "foo" - Path(self.test_file + "/proton").touch() - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(config=toml_path, verb=test_verb), - ): - result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - # Check if a verb was passed - self.assertTrue(vars(result).get("verb"), "Expected a value for --verb") - result_set_env = gamelauncher.set_env_toml(self.env, result) - self.assertTrue(result_set_env is self.env, "Expected the same reference") - # Check for changes after calling - self.assertEqual( - result_set_env["EXE"], - self.test_exe + " " + self.test_file + " " + self.test_file, - ) - self.assertEqual(result_set_env["WINEPREFIX"], self.test_file) - self.assertEqual(result_set_env["PROTONPATH"], self.test_file) - self.assertEqual(result_set_env["GAMEID"], self.test_file) - - self.env["ULWGL_ID"] = self.env["GAMEID"] - self.env["STEAM_COMPAT_APP_ID"] = "0" - - if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): - self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ - self.env["ULWGL_ID"].find("-") + 1 : - ] - - self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] - self.env["SteamGameId"] = self.env["SteamAppId"] - self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() - self.env["PROTONPATH"] = Path(self.env["PROTONPATH"]).expanduser().as_posix() - self.env["STEAM_COMPAT_DATA_PATH"] = self.env["WINEPREFIX"] - self.env["STEAM_COMPAT_SHADER_PATH"] = ( - self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" - ) - self.env["STEAM_COMPAT_INSTALL_PATH"] = ( - Path(self.env["EXE"]).parent.expanduser().as_posix() - ) - self.env["EXE"] = Path(self.env["EXE"]).expanduser().as_posix() - self.env["STEAM_COMPAT_TOOL_PATHS"] = ( - self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() - ) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - - # Create an empty Proton prefix when asked - if not getattr(result, "exe", None) and not getattr(result, "config", None): - self.env["EXE"] = "" - self.env["STEAM_COMPAT_INSTALL_PATH"] = "" - self.verb = "waitforexitandrun" - - for key, val in self.env.items(): - os.environ[key] = val - test_command = gamelauncher.build_command(self.env, test_command, test_verb) - # The verb should be 2nd in the array - self.assertIsInstance(test_command, list, "Expected a List from build_command") - self.assertTrue(test_command[2], self.test_verb) - def test_build_command_nofile(self): """Test build_command. A FileNotFoundError should be raised if $PROTONPATH/proton does not exist - Just test the TOML case for the coverage + NOTE: Also, FileNotFoundError will be raised if the _v2-entry-point (ULWGL) is not in $HOME/.local/share/ULWGL or in cwd """ test_toml = "foo.toml" toml_str = f""" @@ -277,12 +132,11 @@ def test_build_command_nofile(self): prefix = "{self.test_file}" proton = "{self.test_file}" game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] + launch_args = ["{self.test_file}"] exe = "{self.test_exe}" """ toml_path = self.test_file + "/" + test_toml result = None - result_set_env = None test_command = [] Path(toml_path).touch() @@ -294,56 +148,23 @@ def test_build_command_nofile(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - result_set_env = gamelauncher.set_env_toml(self.env, result) - self.assertTrue(result_set_env is self.env, "Expected the same reference") - # Check for changes after calling - self.assertEqual( - result_set_env["EXE"], - self.test_exe + " " + self.test_file + " " + self.test_file, - ) - self.assertEqual(result_set_env["WINEPREFIX"], self.test_file) - self.assertEqual(result_set_env["PROTONPATH"], self.test_file) - self.assertEqual(result_set_env["GAMEID"], self.test_file) - - self.env["ULWGL_ID"] = self.env["GAMEID"] - self.env["STEAM_COMPAT_APP_ID"] = "0" - - if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): - self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ - self.env["ULWGL_ID"].find("-") + 1 : - ] - - self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] - self.env["SteamGameId"] = self.env["SteamAppId"] - self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() - self.env["PROTONPATH"] = Path(self.env["PROTONPATH"]).expanduser().as_posix() - self.env["STEAM_COMPAT_DATA_PATH"] = self.env["WINEPREFIX"] - self.env["STEAM_COMPAT_SHADER_PATH"] = ( - self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" - ) - self.env["STEAM_COMPAT_INSTALL_PATH"] = ( - Path(self.env["EXE"]).parent.expanduser().as_posix() - ) - self.env["EXE"] = Path(self.env["EXE"]).expanduser().as_posix() - self.env["STEAM_COMPAT_TOOL_PATHS"] = ( - self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() - ) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - # Create an empty Proton prefix when asked - if not getattr(result, "exe", None) and not getattr(result, "config", None): - self.env["EXE"] = "" - self.env["STEAM_COMPAT_INSTALL_PATH"] = "" - self.verb = "waitforexitandrun" + # Config + gamelauncher.set_env_toml(self.env, result) + # Prefix + gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + # Env + gamelauncher.set_env(self.env, result) + # Game drive + gamelauncher_plugins.enable_steam_game_drive(self.env) for key, val in self.env.items(): os.environ[key] = val + + # Build with self.assertRaisesRegex(FileNotFoundError, "proton"): - gamelauncher.build_command(self.env, test_command, self.test_verb) + gamelauncher.build_command(self.env, test_command) def test_build_command_toml(self): """Test build_command. @@ -361,8 +182,8 @@ def test_build_command_toml(self): """ toml_path = self.test_file + "/" + test_toml result = None - result_set_env = None test_command = [] + test_command_result = None Path(self.test_file + "/proton").touch() Path(toml_path).touch() @@ -375,60 +196,27 @@ def test_build_command_toml(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - result_set_env = gamelauncher.set_env_toml(self.env, result) - self.assertTrue(result_set_env is self.env, "Expected the same reference") - # Check for changes after calling - self.assertEqual( - result_set_env["EXE"], - self.test_exe + " " + self.test_file + " " + self.test_file, - ) - self.assertEqual(result_set_env["WINEPREFIX"], self.test_file) - self.assertEqual(result_set_env["PROTONPATH"], self.test_file) - self.assertEqual(result_set_env["GAMEID"], self.test_file) - - self.env["ULWGL_ID"] = self.env["GAMEID"] - self.env["STEAM_COMPAT_APP_ID"] = "0" - - if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): - self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ - self.env["ULWGL_ID"].find("-") + 1 : - ] - - self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] - self.env["SteamGameId"] = self.env["SteamAppId"] - self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() - self.env["PROTONPATH"] = Path(self.env["PROTONPATH"]).expanduser().as_posix() - self.env["STEAM_COMPAT_DATA_PATH"] = self.env["WINEPREFIX"] - self.env["STEAM_COMPAT_SHADER_PATH"] = ( - self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" - ) - self.env["STEAM_COMPAT_INSTALL_PATH"] = ( - Path(self.env["EXE"]).parent.expanduser().as_posix() - ) - self.env["EXE"] = Path(self.env["EXE"]).expanduser().as_posix() - self.env["STEAM_COMPAT_TOOL_PATHS"] = ( - self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() - ) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - - # Create an empty Proton prefix when asked - if not getattr(result, "exe", None) and not getattr(result, "config", None): - self.env["EXE"] = "" - self.env["STEAM_COMPAT_INSTALL_PATH"] = "" - self.verb = "waitforexitandrun" + # Config + gamelauncher.set_env_toml(self.env, result) + # Prefix + gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + # Env + gamelauncher.set_env(self.env, result) + # Game drive + gamelauncher_plugins.enable_steam_game_drive(self.env) for key, val in self.env.items(): os.environ[key] = val - test_command = gamelauncher.build_command( - self.env, test_command, self.test_verb + + # Build + test_command_result = gamelauncher.build_command(self.env, test_command) + self.assertTrue( + test_command_result is test_command, "Expected the same reference" ) - self.assertIsInstance(test_command, list, "Expected a List from build_command") - # Verify contents + + # Verify contents of the command entry_point, opt1, verb, opt2, proton, verb2, exe = [*test_command] # The entry point dest could change. Just check if there's a value self.assertTrue(entry_point, "Expected an entry point") @@ -447,90 +235,35 @@ def test_build_command(self): """Test build_command. After parsing valid environment variables set by the user, be sure we do not raise a FileNotFoundError + NOTE: Also, FileNotFoundError will be raised if the _v2-entry-point (ULWGL) is not in $HOME/.local/share/ULWGL or in cwd """ result_args = None - result_check_env = None test_command = [] # Mock the /proton file Path(self.test_file + "/proton").touch() - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= gamelauncher --exe=... - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe, options=self.test_opts), - ): + with patch("sys.argv", ["", self.test_exe]): os.environ["WINEPREFIX"] = self.test_file os.environ["PROTONPATH"] = self.test_file os.environ["GAMEID"] = self.test_file + os.environ["STORE"] = self.test_file + # Args result_args = gamelauncher.parse_args() - self.assertIsInstance( - result_args, Namespace, "parse_args did not return a Namespace" - ) - result_check_env = gamelauncher.check_env(self.env) - self.assertTrue(result_check_env is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) - self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" - ) - result_set_env = gamelauncher.set_env(self.env, result_args) - - # Check for changes after calling - self.assertEqual(result_set_env["WINEPREFIX"], self.test_file) - self.assertEqual(result_set_env["PROTONPATH"], self.test_file) - self.assertEqual(result_set_env["GAMEID"], self.test_file) - # Test for expected EXE with options - self.assertEqual( - self.env.get("EXE"), - "{} {}".format(self.test_exe, self.test_opts), - "Expected the concat EXE and game options to not have trailing spaces", - ) - - self.env["ULWGL_ID"] = self.env["GAMEID"] - self.env["STEAM_COMPAT_APP_ID"] = "0" - - if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): - self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ - self.env["ULWGL_ID"].find("-") + 1 : - ] - - self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] - self.env["SteamGameId"] = self.env["SteamAppId"] - self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() - self.env["PROTONPATH"] = Path(self.env["PROTONPATH"]).expanduser().as_posix() - self.env["STEAM_COMPAT_DATA_PATH"] = self.env["WINEPREFIX"] - self.env["STEAM_COMPAT_SHADER_PATH"] = ( - self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" - ) - self.env["STEAM_COMPAT_INSTALL_PATH"] = ( - Path(self.env["EXE"]).parent.expanduser().as_posix() - ) - self.env["EXE"] = Path(self.env["EXE"]).expanduser().as_posix() - self.env["STEAM_COMPAT_TOOL_PATHS"] = ( - self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() - ) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - - # Create an empty Proton prefix when asked - if not getattr(result_args, "exe", None) and not getattr( - result_args, "config", None - ): - self.env["EXE"] = "" - self.env["STEAM_COMPAT_INSTALL_PATH"] = "" - self.verb = "waitforexitandrun" + # Config + gamelauncher.check_env(self.env) + # Prefix + gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + # Env + gamelauncher.set_env(self.env, result_args) + # Game drive + gamelauncher_plugins.enable_steam_game_drive(self.env) for key, val in self.env.items(): os.environ[key] = val - test_command = gamelauncher.build_command( - self.env, test_command, self.test_verb - ) + # Build + test_command = gamelauncher.build_command(self.env, test_command) self.assertIsInstance(test_command, list, "Expected a List from build_command") self.assertEqual( len(test_command), 7, "Expected 7 elements in the list from build_command" @@ -561,11 +294,13 @@ def test_set_env_toml_config(self): "parse_args", return_value=argparse.Namespace(config=test_file), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") + # Env with self.assertRaisesRegex(FileNotFoundError, test_file): gamelauncher.set_env_toml(self.env, result) @@ -597,13 +332,16 @@ def test_set_env_toml_opts_nofile(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") + # Env gamelauncher.set_env_toml(self.env, result) - # Check if the TOML file we just created + + # Check if its the TOML file we just created self.assertTrue( Path(self.env["EXE"].split(" ")[1]).is_file(), "Expected a file to be appended to the executable", @@ -636,49 +374,16 @@ def test_set_env_toml_nofile(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") + # Env with self.assertRaisesRegex(FileNotFoundError, "exe"): gamelauncher.set_env_toml(self.env, result) - def test_set_env_toml_empty(self): - """Test set_env_toml for empty values not required by parse_args. - - A ValueError should be thrown if 'game_id' is empty - """ - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "" - launch_args = ["{self.test_file}", "{self.test_file}"] - exe = "{self.test_file}" - """ - toml_path = self.test_file + "/" + test_toml - result = None - - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - with self.assertRaisesRegex(ValueError, "game_id"): - gamelauncher.set_env_toml(self.env, result) - def test_set_env_toml_err(self): """Test set_env_toml for valid TOML. @@ -705,17 +410,19 @@ def test_set_env_toml_err(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) + # Env with self.assertRaisesRegex(TOMLDecodeError, "Invalid"): gamelauncher.set_env_toml(self.env, result) def test_set_env_toml_nodir(self): """Test set_env_toml if certain key/value are not a dir. - An IsDirectoryError should be raised if proton or prefix are not directories + An IsDirectoryError should be raised if the following keys are not dir: proton, prefix """ test_toml = "foo.toml" toml_str = f""" @@ -738,18 +445,20 @@ def test_set_env_toml_nodir(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") - with self.assertRaisesRegex(NotADirectoryError, "prefix"): + # Env + with self.assertRaisesRegex(NotADirectoryError, "proton"): gamelauncher.set_env_toml(self.env, result) def test_set_env_toml_tables(self): """Test set_env_toml for expected tables. - A KeyError should be raised if the table 'ulwgl' is absent + A ValueError should be raised if the following tables are absent: ulwgl """ test_toml = "foo.toml" toml_str = f""" @@ -772,12 +481,14 @@ def test_set_env_toml_tables(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") - with self.assertRaisesRegex(KeyError, "ulwgl"): + # Env + with self.assertRaisesRegex(ValueError, "ulwgl"): gamelauncher.set_env_toml(self.env, result) def test_set_env_toml_paths(self): @@ -790,7 +501,7 @@ def test_set_env_toml_paths(self): pattern = r"^/home/[a-zA-Z]+" # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file + # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file path_to_tmp = Path( Path(__file__).cwd().as_posix() + "/" + self.test_file ).as_posix() @@ -831,14 +542,17 @@ def test_set_env_toml_paths(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") + # Env result_set_env = gamelauncher.set_env_toml(self.env, result) self.assertTrue(result_set_env is self.env, "Expected the same reference") - # Check that the paths are still in the unexpanded form + + # Check that the paths are still in the unexpanded form after setting the env # In main, we only expand them after this function exits to prepare for building the command self.assertEqual( self.env["EXE"], unexpanded_exe, "Expected path not to be expanded" @@ -882,233 +596,81 @@ def test_set_env_toml(self): "parse_args", return_value=argparse.Namespace(config=toml_path), ): + # Args result = gamelauncher.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") + # Env result_set_env = gamelauncher.set_env_toml(self.env, result) self.assertTrue(result_set_env is self.env, "Expected the same reference") - - def test_set_env_exe_nofile(self): - """Test set_env when setting no options via --options and appending options to --exe. - - gamelauncher.py --exe "foo -bar" - Options can be appended at the end of the exe if wrapping the value in quotes - No error should be raised if the --exe passed by the user doesn't exist - We trust the user that its legit and only validate the EXE in the TOML case - """ - result_args = None - result_check_env = None - result_set_env = None - - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= gamelauncher --exe=... - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe + " foo"), - ): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = self.test_file - result_args = gamelauncher.parse_args() - self.assertIsInstance( - result_args, Namespace, "parse_args did not return a Namespace" - ) - result_check_env = gamelauncher.check_env(self.env) - self.assertTrue(result_check_env is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) - self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" - ) - result_set_env = gamelauncher.set_env(self.env, result_args) - self.assertTrue(result_set_env is self.env, "Expected the same reference") - self.assertEqual( - self.env["EXE"], - self.test_exe + " foo", - "Expected EXE to be set after passing garbage", - ) - self.assertTrue(Path(self.test_exe).exists(), "Expected the EXE to exist") - self.assertFalse( - Path(self.test_exe + " foo").exists(), - "Expected the concat of EXE and options to not exist", - ) - - def test_set_env_opts_nofile(self): - """Test set_env when an exe's options is a file. - - We allow options that may or may not be legit - No error should be raised in this case and we just check if options are a file - """ - result_args = None - result_check_env = None - result_set_env = None - - # File that will be passed as an option to the exe - test_opts_file = "baz" - Path(test_opts_file).touch() - - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= gamelauncher --exe=... - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe, options=test_opts_file), - ): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = self.test_file - result_args = gamelauncher.parse_args() - self.assertIsInstance( - result_args, Namespace, "parse_args did not return a Namespace" - ) - result_check_env = gamelauncher.check_env(self.env) - self.assertTrue(result_check_env is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) - self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" - ) - result_set_env = gamelauncher.set_env(self.env, result_args) - self.assertTrue(result_set_env is self.env, "Expected the same reference") + self.assertTrue(self.env["EXE"], "Expected EXE to be set") self.assertEqual( self.env["EXE"], - self.test_exe + " " + test_opts_file, - "Expected EXE to be set after appending a file as an option", - ) - # The concat of exe and options shouldn't be a file - self.assertFalse( - Path(self.env["EXE"]).is_file(), - "Expected EXE to not be a file when passing options", - ) - # However each part is a file - self.assertTrue( - Path(test_opts_file).is_file(), - "Expected a file for this test to be used as an option", - ) - self.assertTrue( - Path(self.test_exe).is_file(), - "Expected a file for this test to be used as an option", - ) - Path(test_opts_file).unlink() - - def test_set_env_opts(self): - """Test set_env. - - Ensure no failures and verify that $EXE is set with options passed - """ - result_args = None - result_check_env = None - result = None - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= gamelauncher --exe=... - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe, options=self.test_opts), - ): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = self.test_file - result_args = gamelauncher.parse_args() - self.assertIsInstance( - result_args, Namespace, "parse_args did not return a Namespace" - ) - result_check_env = gamelauncher.check_env(self.env) - self.assertTrue(result_check_env is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" + self.test_exe + " " + " ".join([self.test_file, self.test_file]), + "Expectd GAMEID to be set", ) self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" + self.env["PROTONPATH"], + self.test_file, + "Expected PROTONPATH to be set", ) self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" + self.env["WINEPREFIX"], + self.test_file, + "Expected WINEPREFIX to be set", ) - result = gamelauncher.set_env(self.env, result_args) - self.assertIsInstance(result, dict, "Expected a Dictionary from set_env") - self.assertTrue(self.env.get("EXE"), "Expected EXE to not be empty") self.assertEqual( - self.env.get("EXE"), - self.test_exe + " " + self.test_opts, - "Expected EXE to not have trailing spaces", + self.env["GAMEID"], self.test_file, "Expectd GAMEID to be set" ) - def test_set_env_exe(self): + def test_set_env(self): """Test set_env. Ensure no failures and verify that $EXE """ - result_args = None - result_check_env = None result = None - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= gamelauncher --exe=... - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe), - ): + test_str = "foo" + + # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= gamelauncher ... + with patch("sys.argv", ["", self.test_exe]): os.environ["WINEPREFIX"] = self.test_file os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = self.test_file - result_args = gamelauncher.parse_args() - self.assertIsInstance( - result_args, Namespace, "parse_args did not return a Namespace" - ) - result_check_env = gamelauncher.check_env(self.env) - self.assertTrue(result_check_env is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) + os.environ["GAMEID"] = test_str + os.environ["STORE"] = test_str + os.environ["PROTON_VERB"] = self.test_verb + # Args + result = gamelauncher.parse_args() self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" + result[0:], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" ) - result = gamelauncher.set_env(self.env, result_args) + # Check + gamelauncher.check_env(self.env) + # Prefix + gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + # Env + result = gamelauncher.set_env(self.env, result[0:]) self.assertTrue(result is self.env, "Expected the same reference") - self.assertTrue(self.env.get("EXE"), "Expected EXE to not be empty") - def test_set_env(self): - """Test set_env. + path_exe = Path(self.test_exe).expanduser().as_posix() + path_file = Path(self.test_file).expanduser().as_posix() - Ensure no failures when passing --exe and setting $WINEPREFIX and $PROTONPATH - """ - result_args = None - result_check_env = None - result = None - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= gamelauncher --exe=... - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(game=self.test_file), - ): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = self.test_file - result_args = gamelauncher.parse_args() - self.assertIsInstance(result_args, Namespace) - result_check_env = gamelauncher.check_env(self.env) - self.assertTrue(result_check_env is self.env, "Expected the same reference") + # After calling set_env all paths should be expanded POSIX form + self.assertEqual(self.env["EXE"], path_exe, "Expected EXE to be expanded") + self.assertEqual(self.env["STORE"], test_str, "Expected STORE to be set") self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" + self.env["PROTONPATH"], path_file, "Expected PROTONPATH to be set" ) self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" + self.env["WINEPREFIX"], path_file, "Expected WINEPREFIX to be set" ) + self.assertEqual(self.env["GAMEID"], test_str, "Expected GAMEID to be set") self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" + self.env["PROTON_VERB"], + self.test_verb, + "Expected PROTON_VERB to be set", ) - result = gamelauncher.set_env(self.env, result_args) - self.assertTrue(result is self.env, "Expected the same reference") def test_setup_pfx_symlinks(self): """Test _setup_pfx for valid symlinks. @@ -1130,9 +692,10 @@ def test_setup_pfx_symlinks(self): Path(self.test_file).cwd().as_posix() + "/" + self.test_file ).as_posix(), ) - result = gamelauncher._setup_pfx(unexpanded_path) + result = gamelauncher.setup_pfx(unexpanded_path) + # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file + # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file self.assertIsNone( result, "Expected None when creating symbolic link to WINE prefix and tracked_files file", @@ -1147,6 +710,7 @@ def test_setup_pfx_symlinks(self): self.assertTrue( Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink" ) + # Check if the symlink is in its unexpanded form self.assertEqual( Path(self.test_file + "/pfx").readlink().as_posix(), @@ -1154,7 +718,7 @@ def test_setup_pfx_symlinks(self): ) def test_setup_pfx_paths(self): - """Test _setup_pfx on unexpanded paths. + """Test setup_pfx on unexpanded paths. An error should not be raised when passing paths such as ~/path/to/prefix. """ @@ -1165,9 +729,10 @@ def test_setup_pfx_paths(self): "~", Path(Path(self.test_file).as_posix()).as_posix(), ) - result = gamelauncher._setup_pfx(unexpanded_path) + result = gamelauncher.setup_pfx(unexpanded_path) + # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file + # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file self.assertIsNone( result, "Expected None when creating symbolic link to WINE prefix and tracked_files file", @@ -1181,9 +746,9 @@ def test_setup_pfx_paths(self): ) def test_setup_pfx(self): - """Test _setup_pfx.""" + """Test setup_pfx.""" result = None - result = gamelauncher._setup_pfx(self.test_file) + result = gamelauncher.setup_pfx(self.test_file) self.assertIsNone( result, "Expected None when creating symbolic link to WINE prefix and tracked_files file", @@ -1196,58 +761,6 @@ def test_setup_pfx(self): "Expected tracked_files to be a file", ) - def test_parse_args_verb(self): - """Test parse_args --verb.""" - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe, verb=self.test_verb), - ): - result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertEqual( - result.verb, - self.test_verb, - "Expected the same value when setting --verb", - ) - - def test_parse_args_store(self): - """Test parse_args --store.""" - test_store = "gog" - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe, store=test_store), - ): - result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertEqual( - result.store, - test_store, - "Expected the same value when setting --store", - ) - - def test_parse_args_options(self): - """Test parse_args --options.""" - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_exe, options=self.test_opts), - ): - result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertEqual( - result.options, - self.test_opts, - "Expected the same value when setting --options", - ) - def test_parse_args(self): """Test parse_args with no options. @@ -1270,18 +783,6 @@ def test_parse_args_config(self): result, Namespace, "Expected a Namespace from parse_arg" ) - def test_parse_args_game(self): - """Test parse_args --exe.""" - with patch.object( - gamelauncher, - "parse_args", - return_value=argparse.Namespace(exe=self.test_file), - ): - result = gamelauncher.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - def test_env_proton_dir(self): """Test check_env when $PROTONPATH is not a directory. @@ -1316,8 +817,6 @@ def test_env_wine_dir(self): def test_env_vars_paths(self): """Test check_env when setting unexpanded paths for $WINEPREFIX and $PROTONPATH.""" - # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file pattern = r"^/home/[a-zA-Z]+" path_to_tmp = Path( Path(__file__).cwd().as_posix() + "/" + self.test_file @@ -1370,21 +869,12 @@ def test_env_vars_proton(self): os.environ["WINEPREFIX"] = self.test_file os.environ["GAMEID"] = self.test_file gamelauncher.check_env(self.env) - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) def test_env_vars_wine(self): """Test check_env when setting only $WINEPREFIX.""" with self.assertRaisesRegex(ValueError, "GAMEID"): os.environ["WINEPREFIX"] = self.test_file gamelauncher.check_env(self.env) - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) def test_env_vars_none(self): """Tests check_env when setting no env vars.""" From 9edc4d6d3009f4e6461ea13fa748aee5f7ab6867 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Sun, 11 Feb 2024 10:28:59 -0800 Subject: [PATCH 09/18] gamelauncher.py: update comments --- gamelauncher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamelauncher.py b/gamelauncher.py index ba55860e7..711102c0d 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -46,7 +46,7 @@ def setup_pfx(path: str) -> None: """Create a symlink to the WINE prefix and tracked_files file.""" if not (Path(path + "/pfx")).expanduser().is_symlink(): # When creating the symlink, we want it to be in expanded form when passed unexpanded paths - # Example: pfx -> /home/.wine + # Example: pfx -> /home/foo/.wine # NOTE: When parsing a config file, an error can be raised if the prefix doesn't already exist Path(path + "/pfx").expanduser().symlink_to(Path(path).expanduser()) Path(path + "/tracked_files").expanduser().touch() From e1414dd460066eae1de4fe5f5687495a2c462672 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 00:04:01 -0800 Subject: [PATCH 10/18] Rename ulwgl-run to ulwgl-run-posix --- ulwgl-run => ulwgl-run-posix | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ulwgl-run => ulwgl-run-posix (100%) diff --git a/ulwgl-run b/ulwgl-run-posix similarity index 100% rename from ulwgl-run rename to ulwgl-run-posix From d6eb1c848469e67c7192112269db8c95c16e2693 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 00:12:29 -0800 Subject: [PATCH 11/18] Rename gamelauncher files --- gamelauncher_plugins.py => ulwgl_plugins.py | 0 gamelauncher.py => ulwgl_run.py | 4 +- gamelauncher_test.py => ulwgl_test.py | 142 ++++++++++---------- 3 files changed, 73 insertions(+), 73 deletions(-) rename gamelauncher_plugins.py => ulwgl_plugins.py (100%) rename gamelauncher.py => ulwgl_run.py (99%) rename gamelauncher_test.py => ulwgl_test.py (89%) diff --git a/gamelauncher_plugins.py b/ulwgl_plugins.py similarity index 100% rename from gamelauncher_plugins.py rename to ulwgl_plugins.py diff --git a/gamelauncher.py b/ulwgl_run.py similarity index 99% rename from gamelauncher.py rename to ulwgl_run.py index 711102c0d..1c12610ca 100755 --- a/gamelauncher.py +++ b/ulwgl_run.py @@ -7,7 +7,7 @@ from pathlib import Path import tomllib from typing import Dict, Any, List, Set, Union -import gamelauncher_plugins +import ulwgl_plugins from re import match import subprocess @@ -294,7 +294,7 @@ def main() -> None: # noqa: D103 set_env(env, args) # Game Drive - gamelauncher_plugins.enable_steam_game_drive(env) + ulwgl_plugins.enable_steam_game_drive(env) # Set all environment variables # NOTE: `env` after this block should be read only diff --git a/gamelauncher_test.py b/ulwgl_test.py similarity index 89% rename from gamelauncher_test.py rename to ulwgl_test.py index 45552222b..d90a27c0e 100644 --- a/gamelauncher_test.py +++ b/ulwgl_test.py @@ -1,5 +1,5 @@ import unittest -import gamelauncher +import ulwgl_run import os import argparse from argparse import Namespace @@ -8,11 +8,11 @@ from tomllib import TOMLDecodeError from shutil import rmtree import re -import gamelauncher_plugins +import ulwgl_plugins class TestGameLauncher(unittest.TestCase): - """Test suite for gamelauncher.py. + """Test suite for ulwgl_run.py. TODO: test for mutually exclusive options """ @@ -79,15 +79,15 @@ def test_game_drive_empty(self): os.environ["GAMEID"] = self.test_file os.environ["STORE"] = self.test_file # Args - args = gamelauncher.parse_args() + args = ulwgl_run.parse_args() # Config - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) # Prefix - gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) # Env - gamelauncher.set_env(self.env, args) + ulwgl_run.set_env(self.env, args) # Game drive - result_gamedrive = gamelauncher_plugins.enable_steam_game_drive(self.env) + result_gamedrive = ulwgl_plugins.enable_steam_game_drive(self.env) for key, val in self.env.items(): os.environ[key] = val @@ -144,27 +144,27 @@ def test_build_command_nofile(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() # Config - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) # Prefix - gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) # Env - gamelauncher.set_env(self.env, result) + ulwgl_run.set_env(self.env, result) # Game drive - gamelauncher_plugins.enable_steam_game_drive(self.env) + ulwgl_plugins.enable_steam_game_drive(self.env) for key, val in self.env.items(): os.environ[key] = val # Build with self.assertRaisesRegex(FileNotFoundError, "proton"): - gamelauncher.build_command(self.env, test_command) + ulwgl_run.build_command(self.env, test_command) def test_build_command_toml(self): """Test build_command. @@ -192,26 +192,26 @@ def test_build_command_toml(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() # Config - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) # Prefix - gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) # Env - gamelauncher.set_env(self.env, result) + ulwgl_run.set_env(self.env, result) # Game drive - gamelauncher_plugins.enable_steam_game_drive(self.env) + ulwgl_plugins.enable_steam_game_drive(self.env) for key, val in self.env.items(): os.environ[key] = val # Build - test_command_result = gamelauncher.build_command(self.env, test_command) + test_command_result = ulwgl_run.build_command(self.env, test_command) self.assertTrue( test_command_result is test_command, "Expected the same reference" ) @@ -249,21 +249,21 @@ def test_build_command(self): os.environ["GAMEID"] = self.test_file os.environ["STORE"] = self.test_file # Args - result_args = gamelauncher.parse_args() + result_args = ulwgl_run.parse_args() # Config - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) # Prefix - gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) # Env - gamelauncher.set_env(self.env, result_args) + ulwgl_run.set_env(self.env, result_args) # Game drive - gamelauncher_plugins.enable_steam_game_drive(self.env) + ulwgl_plugins.enable_steam_game_drive(self.env) for key, val in self.env.items(): os.environ[key] = val # Build - test_command = gamelauncher.build_command(self.env, test_command) + test_command = ulwgl_run.build_command(self.env, test_command) self.assertIsInstance(test_command, list, "Expected a List from build_command") self.assertEqual( len(test_command), 7, "Expected 7 elements in the list from build_command" @@ -290,19 +290,19 @@ def test_set_env_toml_config(self): """ test_file = "foo.toml" with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=test_file), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") # Env with self.assertRaisesRegex(FileNotFoundError, test_file): - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) def test_set_env_toml_opts_nofile(self): """Test set_env_toml for options that are a file. @@ -328,18 +328,18 @@ def test_set_env_toml_opts_nofile(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") # Env - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) # Check if its the TOML file we just created self.assertTrue( @@ -370,19 +370,19 @@ def test_set_env_toml_nofile(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") # Env with self.assertRaisesRegex(FileNotFoundError, "exe"): - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) def test_set_env_toml_err(self): """Test set_env_toml for valid TOML. @@ -406,18 +406,18 @@ def test_set_env_toml_err(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) # Env with self.assertRaisesRegex(TOMLDecodeError, "Invalid"): - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) def test_set_env_toml_nodir(self): """Test set_env_toml if certain key/value are not a dir. @@ -441,19 +441,19 @@ def test_set_env_toml_nodir(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") # Env with self.assertRaisesRegex(NotADirectoryError, "proton"): - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) def test_set_env_toml_tables(self): """Test set_env_toml for expected tables. @@ -477,19 +477,19 @@ def test_set_env_toml_tables(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") # Env with self.assertRaisesRegex(ValueError, "ulwgl"): - gamelauncher.set_env_toml(self.env, result) + ulwgl_run.set_env_toml(self.env, result) def test_set_env_toml_paths(self): """Test set_env_toml when specifying unexpanded file path values in the config file. @@ -538,18 +538,18 @@ def test_set_env_toml_paths(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") # Env - result_set_env = gamelauncher.set_env_toml(self.env, result) + result_set_env = ulwgl_run.set_env_toml(self.env, result) self.assertTrue(result_set_env is self.env, "Expected the same reference") # Check that the paths are still in the unexpanded form after setting the env @@ -592,18 +592,18 @@ def test_set_env_toml(self): file.write(toml_str) with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=toml_path), ): # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) self.assertTrue(vars(result).get("config"), "Expected a value for --config") # Env - result_set_env = gamelauncher.set_env_toml(self.env, result) + result_set_env = ulwgl_run.set_env_toml(self.env, result) self.assertTrue(result_set_env is self.env, "Expected the same reference") self.assertTrue(self.env["EXE"], "Expected EXE to be set") self.assertEqual( @@ -633,7 +633,7 @@ def test_set_env(self): result = None test_str = "foo" - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= gamelauncher ... + # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run ... with patch("sys.argv", ["", self.test_exe]): os.environ["WINEPREFIX"] = self.test_file os.environ["PROTONPATH"] = self.test_file @@ -641,16 +641,16 @@ def test_set_env(self): os.environ["STORE"] = test_str os.environ["PROTON_VERB"] = self.test_verb # Args - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertEqual( result[0:], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" ) # Check - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) # Prefix - gamelauncher.setup_pfx(self.env["WINEPREFIX"]) + ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) # Env - result = gamelauncher.set_env(self.env, result[0:]) + result = ulwgl_run.set_env(self.env, result[0:]) self.assertTrue(result is self.env, "Expected the same reference") path_exe = Path(self.test_exe).expanduser().as_posix() @@ -692,7 +692,7 @@ def test_setup_pfx_symlinks(self): Path(self.test_file).cwd().as_posix() + "/" + self.test_file ).as_posix(), ) - result = gamelauncher.setup_pfx(unexpanded_path) + result = ulwgl_run.setup_pfx(unexpanded_path) # Replaces the expanded path to unexpanded # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file @@ -729,7 +729,7 @@ def test_setup_pfx_paths(self): "~", Path(Path(self.test_file).as_posix()).as_posix(), ) - result = gamelauncher.setup_pfx(unexpanded_path) + result = ulwgl_run.setup_pfx(unexpanded_path) # Replaces the expanded path to unexpanded # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file @@ -748,7 +748,7 @@ def test_setup_pfx_paths(self): def test_setup_pfx(self): """Test setup_pfx.""" result = None - result = gamelauncher.setup_pfx(self.test_file) + result = ulwgl_run.setup_pfx(self.test_file) self.assertIsNone( result, "Expected None when creating symbolic link to WINE prefix and tracked_files file", @@ -766,19 +766,19 @@ def test_parse_args(self): There's a requirement to create an empty prefix A SystemExit should be raised in this case: - ./gamelauncher.py + ./ulwgl_run.py """ with self.assertRaises(SystemExit): - gamelauncher.parse_args() + ulwgl_run.parse_args() def test_parse_args_config(self): """Test parse_args --config.""" with patch.object( - gamelauncher, + ulwgl_run, "parse_args", return_value=argparse.Namespace(config=self.test_file), ): - result = gamelauncher.parse_args() + result = ulwgl_run.parse_args() self.assertIsInstance( result, Namespace, "Expected a Namespace from parse_arg" ) @@ -792,7 +792,7 @@ def test_env_proton_dir(self): os.environ["WINEPREFIX"] = self.test_file os.environ["GAMEID"] = self.test_file os.environ["PROTONPATH"] = "./foo" - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) self.assertFalse( Path(os.environ["PROTONPATH"]).is_dir(), "Expected PROTONPATH to not be a directory", @@ -806,7 +806,7 @@ def test_env_wine_dir(self): os.environ["WINEPREFIX"] = "./foo" os.environ["GAMEID"] = self.test_file os.environ["PROTONPATH"] = self.test_file - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) self.assertEqual( Path(os.environ["WINEPREFIX"]).is_dir(), True, @@ -833,7 +833,7 @@ def test_env_vars_paths(self): os.environ["WINEPREFIX"] = unexpanded_path os.environ["GAMEID"] = self.test_file os.environ["PROTONPATH"] = unexpanded_path - result = gamelauncher.check_env(self.env) + result = ulwgl_run.check_env(self.env) self.assertTrue(result is self.env, "Expected the same reference") self.assertEqual( self.env["WINEPREFIX"], unexpanded_path, "Expected WINEPREFIX to be set" @@ -851,7 +851,7 @@ def test_env_vars(self): os.environ["WINEPREFIX"] = self.test_file os.environ["GAMEID"] = self.test_file os.environ["PROTONPATH"] = self.test_file - result = gamelauncher.check_env(self.env) + result = ulwgl_run.check_env(self.env) self.assertTrue(result is self.env, "Expected the same reference") self.assertEqual( self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" @@ -868,18 +868,18 @@ def test_env_vars_proton(self): with self.assertRaisesRegex(ValueError, "PROTONPATH"): os.environ["WINEPREFIX"] = self.test_file os.environ["GAMEID"] = self.test_file - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) def test_env_vars_wine(self): """Test check_env when setting only $WINEPREFIX.""" with self.assertRaisesRegex(ValueError, "GAMEID"): os.environ["WINEPREFIX"] = self.test_file - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) def test_env_vars_none(self): """Tests check_env when setting no env vars.""" with self.assertRaisesRegex(ValueError, "WINEPREFIX"): - gamelauncher.check_env(self.env) + ulwgl_run.check_env(self.env) if __name__ == "__main__": From f28d1709946379413dae06a6789951f3a5ed3673 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 00:13:28 -0800 Subject: [PATCH 12/18] Symlink ulwgl-run to ulwgl_run.py --- ulwgl-run | 1 + 1 file changed, 1 insertion(+) create mode 120000 ulwgl-run diff --git a/ulwgl-run b/ulwgl-run new file mode 120000 index 000000000..cc81dacc0 --- /dev/null +++ b/ulwgl-run @@ -0,0 +1 @@ +ulwgl_run.py \ No newline at end of file From f73ef0f26800923bdfc257bc511212a3b4fd33f0 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 00:27:30 -0800 Subject: [PATCH 13/18] Fix bug that ignores arguments - After specifying the executable, all arguments following the first were ignored unless they were escaped to be interpreted as part of the first argument. --- ulwgl_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ulwgl_run.py b/ulwgl_run.py index 1c12610ca..4077f392e 100755 --- a/ulwgl_run.py +++ b/ulwgl_run.py @@ -39,7 +39,7 @@ def parse_args() -> Union[Namespace, str]: # noqa: D103 if sys.argv[1:][0] in opt_args: return parser.parse_args(sys.argv[1:]) - return sys.argv[1:][0] + return " ".join(sys.argv[1:]) def setup_pfx(path: str) -> None: From 2e8faa1f41fe63439bee6c89d78cd8cabfc05c02 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 00:29:20 -0800 Subject: [PATCH 14/18] Delete print statements --- ulwgl_run.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ulwgl_run.py b/ulwgl_run.py index 4077f392e..f1cbec434 100755 --- a/ulwgl_run.py +++ b/ulwgl_run.py @@ -299,11 +299,9 @@ def main() -> None: # noqa: D103 # Set all environment variables # NOTE: `env` after this block should be read only for key, val in env.items(): - print(f"Setting environment variable: {key}={val}") os.environ[key] = val build_command(env, command) - print(f"The following command will be executed: {command}") subprocess.run(command, check=True, stdout=subprocess.PIPE, text=True) From 6987fde6c316ca1aeabb06af73f1ee7f38de0af1 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:19:58 -0800 Subject: [PATCH 15/18] Change parsing of arguments - Before, the executable and its options were joined as a single string to be executed. Instead, prefer separating them as distinct arguments. As a result, the command as an Array becomes something like: ['/home/foo/.local/share/ULWGL/ULWGL', '--verb', 'waitforexitandrun', '--', '/home/foo/GE-Proton8-30/proton', 'waitforexitandrun', '/home/foo/foo.exe', '-foo', '-bar', '-baz'] --- ulwgl_run.py | 31 ++++++++++++++++++++----------- ulwgl_test.py | 8 ++++++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/ulwgl_run.py b/ulwgl_run.py index f1cbec434..974a2d2fa 100755 --- a/ulwgl_run.py +++ b/ulwgl_run.py @@ -6,13 +6,13 @@ import sys from pathlib import Path import tomllib -from typing import Dict, Any, List, Set, Union +from typing import Dict, Any, List, Set, Union, Tuple import ulwgl_plugins from re import match import subprocess -def parse_args() -> Union[Namespace, str]: # noqa: D103 +def parse_args() -> Union[Namespace, Tuple[str, List[str]]]: # noqa: D103 opt_args: Set[str] = {"--help", "-h", "--config"} exe: str = Path(__file__).name usage: str = f""" @@ -39,7 +39,7 @@ def parse_args() -> Union[Namespace, str]: # noqa: D103 if sys.argv[1:][0] in opt_args: return parser.parse_args(sys.argv[1:]) - return " ".join(sys.argv[1:]) + return (sys.argv[1], sys.argv[2:]) def setup_pfx(path: str) -> None: @@ -122,7 +122,9 @@ def check_env( return env -def set_env(env: Dict[str, str], args: Union[Namespace, str]) -> Dict[str, str]: +def set_env( + env: Dict[str, str], args: Union[Namespace, Tuple[str, List[str]]] +) -> Dict[str, str]: """Set various environment variables for the Steam RT. Filesystem paths will be formatted and expanded as POSIX @@ -145,12 +147,12 @@ def set_env(env: Dict[str, str], args: Union[Namespace, str]) -> Dict[str, str]: # EXE # Empty string for EXE will be used to create a prefix - if isinstance(args, str) and not args: + if isinstance(args, tuple) and isinstance(args[0], str) and not args[0]: env["EXE"] = "" env["STEAM_COMPAT_INSTALL_PATH"] = "" env["PROTON_VERB"] = "waitforexitandrun" - elif isinstance(args, str): - env["EXE"] = Path(args).expanduser().as_posix() + elif isinstance(args, tuple): + env["EXE"] = Path(args[0]).expanduser().as_posix() env["STEAM_COMPAT_INSTALL_PATH"] = Path(env["EXE"]).parent.as_posix() else: # Config branch @@ -223,7 +225,9 @@ def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]: return env -def build_command(env: Dict[str, str], command: List[str]) -> List[str]: +def build_command( + env: Dict[str, str], command: List[str], opts: List[str] = None +) -> List[str]: """Build the command to be executed.""" paths: List[Path] = [ Path(Path().home().as_posix() + "/.local/share/ULWGL/ULWGL"), @@ -256,6 +260,9 @@ def build_command(env: Dict[str, str], command: List[str]) -> List[str]: [Path(env.get("PROTONPATH") + "/proton").as_posix(), verb, env.get("EXE")] ) + if opts: + command.extend([*opts]) + return command @@ -282,12 +289,14 @@ def main() -> None: # noqa: D103 "PROTON_VERB": "", } command: List[str] = [] - # Represents a valid list of current supported Proton verbs - args: Union[Namespace, str] = parse_args() + args: Union[Namespace, Tuple[str, List[str]]] = parse_args() + opts: List[str] = None if isinstance(args, Namespace): set_env_toml(env, args) else: + # Reference the game options + opts = args[1] check_env(env) setup_pfx(env["WINEPREFIX"]) @@ -301,7 +310,7 @@ def main() -> None: # noqa: D103 for key, val in env.items(): os.environ[key] = val - build_command(env, command) + build_command(env, command, opts) subprocess.run(command, check=True, stdout=subprocess.PIPE, text=True) diff --git a/ulwgl_test.py b/ulwgl_test.py index d90a27c0e..ece7f9aae 100644 --- a/ulwgl_test.py +++ b/ulwgl_test.py @@ -633,7 +633,7 @@ def test_set_env(self): result = None test_str = "foo" - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run ... + # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run foo.exe with patch("sys.argv", ["", self.test_exe]): os.environ["WINEPREFIX"] = self.test_file os.environ["PROTONPATH"] = self.test_file @@ -642,8 +642,12 @@ def test_set_env(self): os.environ["PROTON_VERB"] = self.test_verb # Args result = ulwgl_run.parse_args() + self.assertIsInstance(result, tuple, "Expected a tuple") self.assertEqual( - result[0:], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" + result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" + ) + self.assertFalse( + result[1], "Expected an empty list when passing no options" ) # Check ulwgl_run.check_env(self.env) From 1dbcace2098f3feb99deb9831c0c085825823038 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:34:39 -0800 Subject: [PATCH 16/18] ulwgl_test.py: add test for env usage - Test that options and the game are distinct arguments when executing the script via the environment variable usage. --- ulwgl_test.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/ulwgl_test.py b/ulwgl_test.py index ece7f9aae..21d23552b 100644 --- a/ulwgl_test.py +++ b/ulwgl_test.py @@ -625,6 +625,58 @@ def test_set_env_toml(self): self.env["GAMEID"], self.test_file, "Expectd GAMEID to be set" ) + def test_set_env_opts(self): + """Test set_env. + + Ensure no failures and verify options are passed + """ + result = None + test_str = "foo" + + # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run foo.exe -foo + with patch("sys.argv", ["", self.test_exe, test_str]): + os.environ["WINEPREFIX"] = self.test_file + os.environ["PROTONPATH"] = self.test_file + os.environ["GAMEID"] = test_str + os.environ["STORE"] = test_str + os.environ["PROTON_VERB"] = self.test_verb + # Args + result = ulwgl_run.parse_args() + self.assertIsInstance(result, tuple, "Expected a tuple") + self.assertEqual( + result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" + ) + self.assertEqual( + *result[1], test_str, "Expected the test string when passed as an option" + ) + # Check + ulwgl_run.check_env(self.env) + # Prefix + ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) + # Env + result = ulwgl_run.set_env(self.env, result[0:]) + self.assertTrue(result is self.env, "Expected the same reference") + + path_exe = Path(self.test_exe).expanduser().as_posix() + path_file = Path(self.test_file).expanduser().as_posix() + + # After calling set_env all paths should be expanded POSIX form + self.assertEqual(self.env["EXE"], path_exe, "Expected EXE to be expanded") + self.assertEqual(self.env["STORE"], test_str, "Expected STORE to be set") + self.assertEqual( + self.env["PROTONPATH"], path_file, "Expected PROTONPATH to be set" + ) + self.assertEqual( + self.env["WINEPREFIX"], path_file, "Expected WINEPREFIX to be set" + ) + self.assertEqual(self.env["GAMEID"], test_str, "Expected GAMEID to be set") + self.assertEqual( + self.env["PROTON_VERB"], + self.test_verb, + "Expected PROTON_VERB to be set", + ) + + def test_set_env(self): """Test set_env. From d5772c0eb33378234de2b486fb0d5cb5cf59fcad Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:36:03 -0800 Subject: [PATCH 17/18] ulwgl_test.py: update tests for set_env --- ulwgl_test.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ulwgl_test.py b/ulwgl_test.py index 21d23552b..38e2f826e 100644 --- a/ulwgl_test.py +++ b/ulwgl_test.py @@ -628,7 +628,7 @@ def test_set_env_toml(self): def test_set_env_opts(self): """Test set_env. - Ensure no failures and verify options are passed + Ensure no failures and verify that an option is passed to the executable """ result = None test_str = "foo" @@ -643,11 +643,15 @@ def test_set_env_opts(self): # Args result = ulwgl_run.parse_args() self.assertIsInstance(result, tuple, "Expected a tuple") + self.assertIsInstance(result[0], str, "Expected a string") + self.assertIsInstance(result[1], list, "Expected a list as options") self.assertEqual( result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" ) self.assertEqual( - *result[1], test_str, "Expected the test string when passed as an option" + *result[1], + test_str, + "Expected the test string when passed as an option", ) # Check ulwgl_run.check_env(self.env) @@ -676,7 +680,6 @@ def test_set_env_opts(self): "Expected PROTON_VERB to be set", ) - def test_set_env(self): """Test set_env. @@ -695,6 +698,8 @@ def test_set_env(self): # Args result = ulwgl_run.parse_args() self.assertIsInstance(result, tuple, "Expected a tuple") + self.assertIsInstance(result[0], str, "Expected a string") + self.assertIsInstance(result[1], list, "Expected a list as options") self.assertEqual( result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" ) From 14b323e3dc3900e719bf38a0565bf7b7a206d7e6 Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:20:15 -0800 Subject: [PATCH 18/18] Add ULWGL_ID to dict - Also, update the test to check for expected environment variables in the dictionary. Notably, when a valid ULWGL_ID is set, the STEAM_COMPAT_ID and related env var should be the stripped ULWGL_ID. --- ulwgl_run.py | 1 + ulwgl_test.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/ulwgl_run.py b/ulwgl_run.py index 974a2d2fa..c0925f7ce 100755 --- a/ulwgl_run.py +++ b/ulwgl_run.py @@ -287,6 +287,7 @@ def main() -> None: # noqa: D103 "STEAM_RUNTIME_LIBRARY_PATH": "", "STORE": "", "PROTON_VERB": "", + "ULWGL_ID": "", } command: List[str] = [] args: Union[Namespace, Tuple[str, List[str]]] = parse_args() diff --git a/ulwgl_test.py b/ulwgl_test.py index 38e2f826e..496934405 100644 --- a/ulwgl_test.py +++ b/ulwgl_test.py @@ -680,10 +680,105 @@ def test_set_env_opts(self): "Expected PROTON_VERB to be set", ) + def test_set_env_id(self): + """Test set_env. + + Verify that environment variables (dictionary) are set after calling set_env when passing a valid ULWGL_ID + When a valid ULWGL_ID is set, the STEAM_COMPAT_APP_ID variables should be the stripped ULWGL_ID + """ + result = None + test_str = "foo" + ulwgl_id = "ulwgl-271590" + + # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run foo.exe + with patch("sys.argv", ["", self.test_exe]): + os.environ["WINEPREFIX"] = self.test_file + os.environ["PROTONPATH"] = self.test_file + os.environ["GAMEID"] = ulwgl_id + os.environ["STORE"] = test_str + os.environ["PROTON_VERB"] = self.test_verb + # Args + result = ulwgl_run.parse_args() + self.assertIsInstance(result, tuple, "Expected a tuple") + self.assertIsInstance(result[0], str, "Expected a string") + self.assertIsInstance(result[1], list, "Expected a list as options") + self.assertEqual( + result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" + ) + self.assertFalse( + result[1], "Expected an empty list when passing no options" + ) + # Check + ulwgl_run.check_env(self.env) + # Prefix + ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) + # Env + result = ulwgl_run.set_env(self.env, result[0:]) + self.assertTrue(result is self.env, "Expected the same reference") + + path_exe = Path(self.test_exe).expanduser().as_posix() + path_file = Path(self.test_file).expanduser().as_posix() + + # After calling set_env all paths should be expanded POSIX form + self.assertEqual(self.env["EXE"], path_exe, "Expected EXE to be expanded") + self.assertEqual(self.env["STORE"], test_str, "Expected STORE to be set") + self.assertEqual( + self.env["PROTONPATH"], path_file, "Expected PROTONPATH to be set" + ) + self.assertEqual( + self.env["WINEPREFIX"], path_file, "Expected WINEPREFIX to be set" + ) + self.assertEqual(self.env["GAMEID"], ulwgl_id, "Expected GAMEID to be set") + self.assertEqual( + self.env["PROTON_VERB"], + self.test_verb, + "Expected PROTON_VERB to be set", + ) + # ULWGL + self.assertEqual( + self.env["ULWGL_ID"], + self.env["GAMEID"], + "Expected ULWGL_ID to be GAMEID", + ) + self.assertEqual(self.env["ULWGL_ID"], ulwgl_id, "Expected ULWGL_ID") + # Should be stripped -- everything after the hyphen + self.assertEqual( + self.env["STEAM_COMPAT_APP_ID"], + ulwgl_id[ulwgl_id.find("-") + 1 :], + "Expected STEAM_COMPAT_APP_ID to be the stripped ULWGL_ID", + ) + self.assertEqual( + self.env["SteamAppId"], + self.env["STEAM_COMPAT_APP_ID"], + "Expected SteamAppId to be STEAM_COMPAT_APP_ID", + ) + self.assertEqual( + self.env["SteamGameId"], + self.env["SteamAppId"], + "Expected SteamGameId to be STEAM_COMPAT_APP_ID", + ) + + # PATHS + self.assertEqual( + self.env["STEAM_COMPAT_SHADER_PATH"], + self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache", + "Expected STEAM_COMPAT_SHADER_PATH to be set", + ) + self.assertEqual( + self.env["STEAM_COMPAT_TOOL_PATHS"], + self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix(), + "Expected STEAM_COMPAT_TOOL_PATHS to be set", + ) + self.assertEqual( + self.env["STEAM_COMPAT_MOUNTS"], + self.env["STEAM_COMPAT_TOOL_PATHS"], + "Expected STEAM_COMPAT_MOUNTS to be set", + ) + def test_set_env(self): """Test set_env. - Ensure no failures and verify that $EXE + Verify that environment variables (dictionary) are set after calling set_env """ result = None test_str = "foo" @@ -732,6 +827,44 @@ def test_set_env(self): self.test_verb, "Expected PROTON_VERB to be set", ) + # ULWGL + self.assertEqual( + self.env["ULWGL_ID"], + self.env["GAMEID"], + "Expected ULWGL_ID to be GAMEID", + ) + self.assertEqual( + self.env["STEAM_COMPAT_APP_ID"], + "0", + "Expected STEAM_COMPAT_APP_ID to be 0", + ) + self.assertEqual( + self.env["SteamAppId"], + self.env["STEAM_COMPAT_APP_ID"], + "Expected SteamAppId to be STEAM_COMPAT_APP_ID", + ) + self.assertEqual( + self.env["SteamGameId"], + self.env["SteamAppId"], + "Expected SteamGameId to be STEAM_COMPAT_APP_ID", + ) + + # PATHS + self.assertEqual( + self.env["STEAM_COMPAT_SHADER_PATH"], + self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache", + "Expected STEAM_COMPAT_SHADER_PATH to be set", + ) + self.assertEqual( + self.env["STEAM_COMPAT_TOOL_PATHS"], + self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix(), + "Expected STEAM_COMPAT_TOOL_PATHS to be set", + ) + self.assertEqual( + self.env["STEAM_COMPAT_MOUNTS"], + self.env["STEAM_COMPAT_TOOL_PATHS"], + "Expected STEAM_COMPAT_MOUNTS to be set", + ) def test_setup_pfx_symlinks(self): """Test _setup_pfx for valid symlinks.