mirror of
https://github.com/tcsenpai/UWINE.git
synced 2025-06-06 11:35:20 +00:00
Merge pull request #38 from R1kaB3rN/toml-plugin
Make parsing configuration files a plugin
This commit is contained in:
commit
b97c9af7c0
11
.github/workflows/ulwgl-python.yml
vendored
11
.github/workflows/ulwgl-python.yml
vendored
@ -14,13 +14,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# tomllib requires Python 3.11
|
# tomllib requires Python 3.11
|
||||||
version: ["3.11", "3.12"]
|
# Ubuntu latest (Jammy) provides Python 3.10
|
||||||
|
version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python 3.11
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.version }}
|
python-version: ${{ matrix.version }}
|
||||||
@ -32,5 +33,7 @@ jobs:
|
|||||||
pip install ruff
|
pip install ruff
|
||||||
ruff --output-format github ulwgl_*.py
|
ruff --output-format github ulwgl_*.py
|
||||||
- name: Test with unittest
|
- name: Test with unittest
|
||||||
run: |
|
run: python3 ulwgl_test.py
|
||||||
python3 ulwgl_test.py
|
- name: Test with unittest for plugins
|
||||||
|
if: ${{ matrix.version == '3.11' || matrix.version == '3.12' }}
|
||||||
|
run: python3 ulwgl_test_plugins.py
|
||||||
|
@ -1,6 +1,96 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Set
|
from typing import Dict, Set, Any, List
|
||||||
|
from argparse import Namespace
|
||||||
|
|
||||||
|
|
||||||
|
def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]:
|
||||||
|
"""Read a TOML file then sets the environment variables for the Steam RT.
|
||||||
|
|
||||||
|
In the TOML file, certain keys map to Steam RT environment variables. For example:
|
||||||
|
proton -> $PROTONPATH
|
||||||
|
prefix -> $WINEPREFIX
|
||||||
|
game_id -> $GAMEID
|
||||||
|
exe -> $EXE
|
||||||
|
At the moment we expect the tables: 'ulwgl'
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
msg: str = "tomllib requires Python 3.11"
|
||||||
|
raise ModuleNotFoundError(msg)
|
||||||
|
|
||||||
|
toml: Dict[str, Any] = None
|
||||||
|
path_config: str = Path(getattr(args, "config", None)).expanduser().as_posix()
|
||||||
|
|
||||||
|
if not Path(path_config).is_file():
|
||||||
|
msg: str = "Path to configuration is not a file: " + getattr(
|
||||||
|
args, "config", None
|
||||||
|
)
|
||||||
|
raise FileNotFoundError(msg)
|
||||||
|
|
||||||
|
with Path(path_config).open(mode="rb") as file:
|
||||||
|
toml = tomllib.load(file)
|
||||||
|
|
||||||
|
_check_env_toml(env, toml)
|
||||||
|
|
||||||
|
for key, val in toml["ulwgl"].items():
|
||||||
|
if key == "prefix":
|
||||||
|
env["WINEPREFIX"] = val
|
||||||
|
elif key == "game_id":
|
||||||
|
env["GAMEID"] = val
|
||||||
|
elif key == "proton":
|
||||||
|
env["PROTONPATH"] = val
|
||||||
|
elif key == "store":
|
||||||
|
env["STORE"] = val
|
||||||
|
elif key == "exe":
|
||||||
|
if toml.get("ulwgl").get("launch_args"):
|
||||||
|
env["EXE"] = val + " " + " ".join(toml.get("ulwgl").get("launch_args"))
|
||||||
|
else:
|
||||||
|
env["EXE"] = val
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def _check_env_toml(env: Dict[str, str], toml: Dict[str, Any]):
|
||||||
|
"""Check for required or empty key/value pairs when reading a TOML config.
|
||||||
|
|
||||||
|
NOTE: Casing matters in the config and we do not 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
|
||||||
|
|
||||||
|
|
||||||
def enable_steam_game_drive(env: Dict[str, str]) -> Dict[str, str]:
|
def enable_steam_game_drive(env: Dict[str, str]) -> Dict[str, str]:
|
||||||
|
89
ulwgl_run.py
89
ulwgl_run.py
@ -6,9 +6,8 @@ from traceback import print_exception
|
|||||||
from argparse import ArgumentParser, Namespace
|
from argparse import ArgumentParser, Namespace
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import tomllib
|
|
||||||
from typing import Dict, Any, List, Set, Union, Tuple
|
from typing import Dict, Any, List, Set, Union, Tuple
|
||||||
import ulwgl_plugins
|
from ulwgl_plugins import enable_steam_game_drive, set_env_toml
|
||||||
from re import match
|
from re import match
|
||||||
import subprocess
|
import subprocess
|
||||||
from ulwgl_dl_util import get_ulwgl_proton
|
from ulwgl_dl_util import get_ulwgl_proton
|
||||||
@ -42,7 +41,7 @@ example usage:
|
|||||||
epilog=usage,
|
epilog=usage,
|
||||||
formatter_class=argparse.RawTextHelpFormatter,
|
formatter_class=argparse.RawTextHelpFormatter,
|
||||||
)
|
)
|
||||||
parser.add_argument("--config", help="path to TOML file")
|
parser.add_argument("--config", help="path to TOML file (requires Python 3.11)")
|
||||||
|
|
||||||
if not 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"
|
err: str = "Please see project README.md for more info and examples.\nhttps://github.com/Open-Wine-Components/ULWGL-launcher"
|
||||||
@ -80,45 +79,6 @@ def check_env(
|
|||||||
|
|
||||||
WINEPREFIX, GAMEID and PROTONPATH are strictly required.
|
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 "GAMEID" not in os.environ:
|
if "GAMEID" not in os.environ:
|
||||||
err: str = "Environment variable not set: GAMEID"
|
err: str = "Environment variable not set: GAMEID"
|
||||||
raise ValueError(err)
|
raise ValueError(err)
|
||||||
@ -218,47 +178,6 @@ def set_env(
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]:
|
|
||||||
"""Read a TOML file then sets the environment variables for the Steam RT.
|
|
||||||
|
|
||||||
In the TOML file, certain keys map to Steam RT environment variables. For example:
|
|
||||||
proton -> $PROTONPATH
|
|
||||||
prefix -> $WINEPREFIX
|
|
||||||
game_id -> $GAMEID
|
|
||||||
exe -> $EXE
|
|
||||||
At the moment we expect the tables: 'ulwgl'
|
|
||||||
"""
|
|
||||||
toml: Dict[str, Any] = None
|
|
||||||
path_config: str = Path(getattr(args, "config", None)).expanduser().as_posix()
|
|
||||||
|
|
||||||
if not Path(path_config).is_file():
|
|
||||||
msg: str = "Path to configuration is not a file: " + getattr(
|
|
||||||
args, "config", None
|
|
||||||
)
|
|
||||||
raise FileNotFoundError(msg)
|
|
||||||
|
|
||||||
with Path(path_config).open(mode="rb") as file:
|
|
||||||
toml = tomllib.load(file)
|
|
||||||
|
|
||||||
check_env(env, toml)
|
|
||||||
|
|
||||||
for key, val in toml["ulwgl"].items():
|
|
||||||
if key == "prefix":
|
|
||||||
env["WINEPREFIX"] = val
|
|
||||||
elif key == "game_id":
|
|
||||||
env["GAMEID"] = val
|
|
||||||
elif key == "proton":
|
|
||||||
env["PROTONPATH"] = val
|
|
||||||
elif key == "store":
|
|
||||||
env["STORE"] = val
|
|
||||||
elif key == "exe":
|
|
||||||
if toml.get("ulwgl").get("launch_args"):
|
|
||||||
env["EXE"] = val + " " + " ".join(toml.get("ulwgl").get("launch_args"))
|
|
||||||
else:
|
|
||||||
env["EXE"] = val
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
def build_command(
|
def build_command(
|
||||||
env: Dict[str, str], command: List[str], opts: List[str] = None
|
env: Dict[str, str], command: List[str], opts: List[str] = None
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
@ -331,7 +250,7 @@ def main() -> int: # noqa: D103
|
|||||||
args: Union[Namespace, Tuple[str, List[str]]] = parse_args()
|
args: Union[Namespace, Tuple[str, List[str]]] = parse_args()
|
||||||
opts: List[str] = None
|
opts: List[str] = None
|
||||||
|
|
||||||
if isinstance(args, Namespace):
|
if isinstance(args, Namespace) and getattr(args, "config", None):
|
||||||
set_env_toml(env, args)
|
set_env_toml(env, args)
|
||||||
else:
|
else:
|
||||||
# Reference the game options
|
# Reference the game options
|
||||||
@ -342,7 +261,7 @@ def main() -> int: # noqa: D103
|
|||||||
set_env(env, args)
|
set_env(env, args)
|
||||||
|
|
||||||
# Game Drive
|
# Game Drive
|
||||||
ulwgl_plugins.enable_steam_game_drive(env)
|
enable_steam_game_drive(env)
|
||||||
|
|
||||||
# Set all environment variables
|
# Set all environment variables
|
||||||
# NOTE: `env` after this block should be read only
|
# NOTE: `env` after this block should be read only
|
||||||
|
455
ulwgl_test.py
455
ulwgl_test.py
@ -5,7 +5,6 @@ import argparse
|
|||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tomllib import TOMLDecodeError
|
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
import re
|
import re
|
||||||
import ulwgl_plugins
|
import ulwgl_plugins
|
||||||
@ -14,10 +13,7 @@ import tarfile
|
|||||||
|
|
||||||
|
|
||||||
class TestGameLauncher(unittest.TestCase):
|
class TestGameLauncher(unittest.TestCase):
|
||||||
"""Test suite for ulwgl_run.py.
|
"""Test suite for ulwgl_run.py."""
|
||||||
|
|
||||||
TODO: test for mutually exclusive options
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Create the test directory, exe and environment variables."""
|
"""Create the test directory, exe and environment variables."""
|
||||||
@ -525,117 +521,6 @@ class TestGameLauncher(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertFalse(self.env["EXE"], "Expected EXE to be empty on empty string")
|
self.assertFalse(self.env["EXE"], "Expected EXE to be empty on empty string")
|
||||||
|
|
||||||
def test_build_command_nofile(self):
|
|
||||||
"""Test build_command.
|
|
||||||
|
|
||||||
A FileNotFoundError should be raised if $PROTONPATH/proton does not exist
|
|
||||||
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"""
|
|
||||||
[ulwgl]
|
|
||||||
prefix = "{self.test_file}"
|
|
||||||
proton = "{self.test_file}"
|
|
||||||
game_id = "{self.test_file}"
|
|
||||||
launch_args = ["{self.test_file}"]
|
|
||||||
exe = "{self.test_exe}"
|
|
||||||
"""
|
|
||||||
toml_path = self.test_file + "/" + test_toml
|
|
||||||
result = None
|
|
||||||
test_command = []
|
|
||||||
Path(toml_path).touch()
|
|
||||||
|
|
||||||
with Path(toml_path).open(mode="w") as file:
|
|
||||||
file.write(toml_str)
|
|
||||||
|
|
||||||
with patch.object(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# Args
|
|
||||||
result = ulwgl_run.parse_args()
|
|
||||||
# Config
|
|
||||||
ulwgl_run.set_env_toml(self.env, result)
|
|
||||||
# Prefix
|
|
||||||
ulwgl_run.setup_pfx(self.env["WINEPREFIX"])
|
|
||||||
# Env
|
|
||||||
ulwgl_run.set_env(self.env, result)
|
|
||||||
# Game drive
|
|
||||||
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"):
|
|
||||||
ulwgl_run.build_command(self.env, test_command)
|
|
||||||
|
|
||||||
def test_build_command_toml(self):
|
|
||||||
"""Test build_command.
|
|
||||||
|
|
||||||
After parsing a valid TOML file, be sure we do not raise a FileNotFoundError
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
test_command = []
|
|
||||||
test_command_result = None
|
|
||||||
|
|
||||||
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(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# Args
|
|
||||||
result = ulwgl_run.parse_args()
|
|
||||||
# Config
|
|
||||||
ulwgl_run.set_env_toml(self.env, result)
|
|
||||||
# Prefix
|
|
||||||
ulwgl_run.setup_pfx(self.env["WINEPREFIX"])
|
|
||||||
# Env
|
|
||||||
ulwgl_run.set_env(self.env, result)
|
|
||||||
# Game drive
|
|
||||||
ulwgl_plugins.enable_steam_game_drive(self.env)
|
|
||||||
|
|
||||||
for key, val in self.env.items():
|
|
||||||
os.environ[key] = val
|
|
||||||
|
|
||||||
# Build
|
|
||||||
test_command_result = ulwgl_run.build_command(self.env, test_command)
|
|
||||||
self.assertTrue(
|
|
||||||
test_command_result is test_command, "Expected the same reference"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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")
|
|
||||||
self.assertEqual(opt1, "--verb", "Expected --verb")
|
|
||||||
self.assertEqual(verb, self.test_verb, "Expected a verb")
|
|
||||||
self.assertEqual(opt2, "--", "Expected --")
|
|
||||||
self.assertEqual(
|
|
||||||
proton,
|
|
||||||
Path(self.env.get("PROTONPATH") + "/proton").as_posix(),
|
|
||||||
"Expected the proton file",
|
|
||||||
)
|
|
||||||
self.assertEqual(verb2, self.test_verb, "Expected a verb")
|
|
||||||
self.assertEqual(exe, self.env["EXE"], "Expected the EXE")
|
|
||||||
|
|
||||||
def test_build_command(self):
|
def test_build_command(self):
|
||||||
"""Test build_command.
|
"""Test build_command.
|
||||||
|
|
||||||
@ -688,344 +573,6 @@ class TestGameLauncher(unittest.TestCase):
|
|||||||
self.assertEqual(verb2, self.test_verb, "Expected a verb")
|
self.assertEqual(verb2, self.test_verb, "Expected a verb")
|
||||||
self.assertEqual(exe, self.env["EXE"], "Expected the EXE")
|
self.assertEqual(exe, self.env["EXE"], "Expected the EXE")
|
||||||
|
|
||||||
def test_set_env_toml_config(self):
|
|
||||||
"""Test set_env_toml when passing a configuration file.
|
|
||||||
|
|
||||||
An FileNotFoundError should be raised when passing a TOML file that doesn't exist
|
|
||||||
"""
|
|
||||||
test_file = "foo.toml"
|
|
||||||
with patch.object(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=test_file),
|
|
||||||
):
|
|
||||||
# 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):
|
|
||||||
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.
|
|
||||||
|
|
||||||
An error should not be raised if a launch argument is a file
|
|
||||||
We allow this behavior to give users flexibility at the cost of security
|
|
||||||
"""
|
|
||||||
test_toml = "foo.toml"
|
|
||||||
toml_path = self.test_file + "/" + test_toml
|
|
||||||
toml_str = f"""
|
|
||||||
[ulwgl]
|
|
||||||
prefix = "{self.test_file}"
|
|
||||||
proton = "{self.test_file}"
|
|
||||||
game_id = "{self.test_file}"
|
|
||||||
launch_args = ["{toml_path}"]
|
|
||||||
exe = "{self.test_exe}"
|
|
||||||
"""
|
|
||||||
result = None
|
|
||||||
|
|
||||||
Path(toml_path).touch()
|
|
||||||
|
|
||||||
with Path(toml_path).open(mode="w") as file:
|
|
||||||
file.write(toml_str)
|
|
||||||
|
|
||||||
with patch.object(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# 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
|
|
||||||
ulwgl_run.set_env_toml(self.env, result)
|
|
||||||
|
|
||||||
# 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",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_set_env_toml_nofile(self):
|
|
||||||
"""Test set_env_toml for values that are not a file.
|
|
||||||
|
|
||||||
A FileNotFoundError should be raised if the 'exe' is not a file
|
|
||||||
"""
|
|
||||||
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 = "./bar"
|
|
||||||
"""
|
|
||||||
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(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# 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"):
|
|
||||||
ulwgl_run.set_env_toml(self.env, result)
|
|
||||||
|
|
||||||
def test_set_env_toml_err(self):
|
|
||||||
"""Test set_env_toml for valid TOML.
|
|
||||||
|
|
||||||
A TOMLDecodeError should be raised for invalid values
|
|
||||||
"""
|
|
||||||
test_toml = "foo.toml"
|
|
||||||
toml_str = f"""
|
|
||||||
[ulwgl]
|
|
||||||
prefix = [[
|
|
||||||
proton = "{self.test_file}"
|
|
||||||
game_id = "{self.test_file}"
|
|
||||||
launch_args = ["{self.test_file}", "{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(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# Args
|
|
||||||
result = ulwgl_run.parse_args()
|
|
||||||
self.assertIsInstance(
|
|
||||||
result, Namespace, "Expected a Namespace from parse_arg"
|
|
||||||
)
|
|
||||||
# Env
|
|
||||||
with self.assertRaisesRegex(TOMLDecodeError, "Invalid"):
|
|
||||||
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.
|
|
||||||
|
|
||||||
An IsDirectoryError should be raised if the following keys are not dir: proton, prefix
|
|
||||||
"""
|
|
||||||
test_toml = "foo.toml"
|
|
||||||
toml_str = f"""
|
|
||||||
[ulwgl]
|
|
||||||
prefix = "foo"
|
|
||||||
proton = "foo"
|
|
||||||
game_id = "{self.test_file}"
|
|
||||||
launch_args = ["{self.test_file}", "{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(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# 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"):
|
|
||||||
ulwgl_run.set_env_toml(self.env, result)
|
|
||||||
|
|
||||||
def test_set_env_toml_tables(self):
|
|
||||||
"""Test set_env_toml for expected tables.
|
|
||||||
|
|
||||||
A ValueError should be raised if the following tables are absent: ulwgl
|
|
||||||
"""
|
|
||||||
test_toml = "foo.toml"
|
|
||||||
toml_str = f"""
|
|
||||||
[foo]
|
|
||||||
prefix = "{self.test_file}"
|
|
||||||
proton = "{self.test_file}"
|
|
||||||
game_id = "{self.test_file}"
|
|
||||||
launch_args = ["{self.test_file}", "{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(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# 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"):
|
|
||||||
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.
|
|
||||||
|
|
||||||
Example: ~/Games/foo.exe
|
|
||||||
An error should not be raised when passing unexpanded paths to the config file as well as the prefix, proton and exe keys
|
|
||||||
"""
|
|
||||||
test_toml = "foo.toml"
|
|
||||||
pattern = r"^/home/[\w\d]+" # Expects only unicode decimals and alphanumerics
|
|
||||||
|
|
||||||
# Replaces the expanded path to unexpanded
|
|
||||||
# Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file
|
|
||||||
path_to_tmp = Path(__file__).cwd().joinpath(self.test_file).as_posix()
|
|
||||||
path_to_exe = Path(__file__).cwd().joinpath(self.test_exe).as_posix()
|
|
||||||
|
|
||||||
# Replace /home/[a-zA-Z]+ substring in path with tilda
|
|
||||||
unexpanded_path = re.sub(
|
|
||||||
pattern,
|
|
||||||
"~",
|
|
||||||
path_to_tmp,
|
|
||||||
)
|
|
||||||
unexpanded_exe = re.sub(
|
|
||||||
pattern,
|
|
||||||
"~",
|
|
||||||
path_to_exe,
|
|
||||||
)
|
|
||||||
toml_str = f"""
|
|
||||||
[ulwgl]
|
|
||||||
prefix = "{unexpanded_path}"
|
|
||||||
proton = "{unexpanded_path}"
|
|
||||||
game_id = "{unexpanded_path}"
|
|
||||||
exe = "{unexpanded_exe}"
|
|
||||||
"""
|
|
||||||
# Path to TOML in unexpanded form
|
|
||||||
toml_path = unexpanded_path + "/" + test_toml
|
|
||||||
result = None
|
|
||||||
result_set_env = None
|
|
||||||
|
|
||||||
Path(toml_path).expanduser().touch()
|
|
||||||
|
|
||||||
with Path(toml_path).expanduser().open(mode="w") as file:
|
|
||||||
file.write(toml_str)
|
|
||||||
|
|
||||||
with patch.object(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# 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 = 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
|
|
||||||
# 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"
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self.env["PROTONPATH"],
|
|
||||||
unexpanded_path,
|
|
||||||
"Expected path not to be expanded",
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self.env["WINEPREFIX"],
|
|
||||||
unexpanded_path,
|
|
||||||
"Expected path not to be expanded",
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self.env["GAMEID"], unexpanded_path, "Expectd path not to be expanded"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_set_env_toml(self):
|
|
||||||
"""Test set_env_toml."""
|
|
||||||
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
|
|
||||||
|
|
||||||
Path(toml_path).touch()
|
|
||||||
|
|
||||||
with Path(toml_path).open(mode="w") as file:
|
|
||||||
file.write(toml_str)
|
|
||||||
|
|
||||||
with patch.object(
|
|
||||||
ulwgl_run,
|
|
||||||
"parse_args",
|
|
||||||
return_value=argparse.Namespace(config=toml_path),
|
|
||||||
):
|
|
||||||
# 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 = 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(
|
|
||||||
self.env["EXE"],
|
|
||||||
self.test_exe + " " + " ".join([self.test_file, self.test_file]),
|
|
||||||
"Expectd GAMEID to be set",
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self.env["PROTONPATH"],
|
|
||||||
self.test_file,
|
|
||||||
"Expected PROTONPATH to be set",
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self.env["WINEPREFIX"],
|
|
||||||
self.test_file,
|
|
||||||
"Expected WINEPREFIX to be set",
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self.env["GAMEID"], self.test_file, "Expectd GAMEID to be set"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_set_env_opts(self):
|
def test_set_env_opts(self):
|
||||||
"""Test set_env.
|
"""Test set_env.
|
||||||
|
|
||||||
|
533
ulwgl_test_plugins.py
Normal file
533
ulwgl_test_plugins.py
Normal file
@ -0,0 +1,533 @@
|
|||||||
|
import unittest
|
||||||
|
import ulwgl_run
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
from argparse import Namespace
|
||||||
|
from unittest.mock import patch
|
||||||
|
from pathlib import Path
|
||||||
|
from tomllib import TOMLDecodeError
|
||||||
|
from shutil import rmtree
|
||||||
|
import re
|
||||||
|
import ulwgl_plugins
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
|
||||||
|
class TestGameLauncherPlugins(unittest.TestCase):
|
||||||
|
"""Test suite ulwgl_run.py plugins."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Create the test directory, exe and environment variables."""
|
||||||
|
self.env = {
|
||||||
|
"WINEPREFIX": "",
|
||||||
|
"GAMEID": "",
|
||||||
|
"PROTON_CRASH_REPORT_DIR": "/tmp/ULWGL_crashreports",
|
||||||
|
"PROTONPATH": "",
|
||||||
|
"STEAM_COMPAT_APP_ID": "",
|
||||||
|
"STEAM_COMPAT_TOOL_PATHS": "",
|
||||||
|
"STEAM_COMPAT_LIBRARY_PATHS": "",
|
||||||
|
"STEAM_COMPAT_MOUNTS": "",
|
||||||
|
"STEAM_COMPAT_INSTALL_PATH": "",
|
||||||
|
"STEAM_COMPAT_CLIENT_INSTALL_PATH": "",
|
||||||
|
"STEAM_COMPAT_DATA_PATH": "",
|
||||||
|
"STEAM_COMPAT_SHADER_PATH": "",
|
||||||
|
"FONTCONFIG_PATH": "",
|
||||||
|
"EXE": "",
|
||||||
|
"SteamAppId": "",
|
||||||
|
"SteamGameId": "",
|
||||||
|
"STEAM_RUNTIME_LIBRARY_PATH": "",
|
||||||
|
"ULWGL_ID": "",
|
||||||
|
"STORE": "",
|
||||||
|
"PROTON_VERB": "",
|
||||||
|
}
|
||||||
|
self.test_opts = "-foo -bar"
|
||||||
|
# Proton verb
|
||||||
|
# Used when testing build_command
|
||||||
|
self.test_verb = "waitforexitandrun"
|
||||||
|
# Test directory
|
||||||
|
self.test_file = "./tmp.AKN6tnueyO"
|
||||||
|
# Executable
|
||||||
|
self.test_exe = self.test_file + "/" + "foo"
|
||||||
|
# Cache
|
||||||
|
self.test_cache = Path("./tmp.ND7tcK5m3K")
|
||||||
|
# Steam compat dir
|
||||||
|
self.test_compat = Path("./tmp.1A5cflhwQa")
|
||||||
|
# ULWGL-Proton dir
|
||||||
|
self.test_proton_dir = Path("ULWGL-Proton-jPTxUsKDdn")
|
||||||
|
# ULWGL-Proton release
|
||||||
|
self.test_archive = Path(self.test_cache).joinpath(
|
||||||
|
f"{self.test_proton_dir}.tar.gz"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.test_cache.mkdir(exist_ok=True)
|
||||||
|
self.test_compat.mkdir(exist_ok=True)
|
||||||
|
self.test_proton_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Mock the proton file in the dir
|
||||||
|
self.test_proton_dir.joinpath("proton").touch(exist_ok=True)
|
||||||
|
|
||||||
|
# Mock the release downloaded in the cache: tmp.5HYdpddgvs/ULWGL-Proton-jPTxUsKDdn.tar.gz
|
||||||
|
# Expected directory structure within the archive:
|
||||||
|
#
|
||||||
|
# +-- ULWGL-Proton-5HYdpddgvs (root directory)
|
||||||
|
# | +-- proton (normal file)
|
||||||
|
with tarfile.open(self.test_archive.as_posix(), "w:gz") as tar:
|
||||||
|
tar.add(
|
||||||
|
self.test_proton_dir.as_posix(), arcname=self.test_proton_dir.as_posix()
|
||||||
|
)
|
||||||
|
|
||||||
|
Path(self.test_file).mkdir(exist_ok=True)
|
||||||
|
Path(self.test_exe).touch()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Unset environment variables and delete test files after each test."""
|
||||||
|
for key, val in self.env.items():
|
||||||
|
if key in os.environ:
|
||||||
|
os.environ.pop(key)
|
||||||
|
|
||||||
|
if Path(self.test_file).exists():
|
||||||
|
rmtree(self.test_file)
|
||||||
|
|
||||||
|
if self.test_cache.exists():
|
||||||
|
rmtree(self.test_cache.as_posix())
|
||||||
|
|
||||||
|
if self.test_compat.exists():
|
||||||
|
rmtree(self.test_compat.as_posix())
|
||||||
|
|
||||||
|
if self.test_proton_dir.exists():
|
||||||
|
rmtree(self.test_proton_dir.as_posix())
|
||||||
|
|
||||||
|
def test_build_command_nofile(self):
|
||||||
|
"""Test build_command.
|
||||||
|
|
||||||
|
A FileNotFoundError should be raised if $PROTONPATH/proton does not exist
|
||||||
|
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"""
|
||||||
|
[ulwgl]
|
||||||
|
prefix = "{self.test_file}"
|
||||||
|
proton = "{self.test_file}"
|
||||||
|
game_id = "{self.test_file}"
|
||||||
|
launch_args = ["{self.test_file}"]
|
||||||
|
exe = "{self.test_exe}"
|
||||||
|
"""
|
||||||
|
toml_path = self.test_file + "/" + test_toml
|
||||||
|
result = None
|
||||||
|
test_command = []
|
||||||
|
Path(toml_path).touch()
|
||||||
|
|
||||||
|
with Path(toml_path).open(mode="w") as file:
|
||||||
|
file.write(toml_str)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# Args
|
||||||
|
result = ulwgl_run.parse_args()
|
||||||
|
# Config
|
||||||
|
ulwgl_plugins.set_env_toml(self.env, result)
|
||||||
|
# Prefix
|
||||||
|
ulwgl_run.setup_pfx(self.env["WINEPREFIX"])
|
||||||
|
# Env
|
||||||
|
ulwgl_run.set_env(self.env, result)
|
||||||
|
# Game drive
|
||||||
|
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"):
|
||||||
|
ulwgl_run.build_command(self.env, test_command)
|
||||||
|
|
||||||
|
def test_build_command_toml(self):
|
||||||
|
"""Test build_command.
|
||||||
|
|
||||||
|
After parsing a valid TOML file, be sure we do not raise a FileNotFoundError
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
test_command = []
|
||||||
|
test_command_result = None
|
||||||
|
|
||||||
|
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(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# Args
|
||||||
|
result = ulwgl_run.parse_args()
|
||||||
|
# Config
|
||||||
|
ulwgl_plugins.set_env_toml(self.env, result)
|
||||||
|
# Prefix
|
||||||
|
ulwgl_run.setup_pfx(self.env["WINEPREFIX"])
|
||||||
|
# Env
|
||||||
|
ulwgl_run.set_env(self.env, result)
|
||||||
|
# Game drive
|
||||||
|
ulwgl_plugins.enable_steam_game_drive(self.env)
|
||||||
|
|
||||||
|
for key, val in self.env.items():
|
||||||
|
os.environ[key] = val
|
||||||
|
|
||||||
|
# Build
|
||||||
|
test_command_result = ulwgl_run.build_command(self.env, test_command)
|
||||||
|
self.assertTrue(
|
||||||
|
test_command_result is test_command, "Expected the same reference"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
self.assertEqual(opt1, "--verb", "Expected --verb")
|
||||||
|
self.assertEqual(verb, self.test_verb, "Expected a verb")
|
||||||
|
self.assertEqual(opt2, "--", "Expected --")
|
||||||
|
self.assertEqual(
|
||||||
|
proton,
|
||||||
|
Path(self.env.get("PROTONPATH") + "/proton").as_posix(),
|
||||||
|
"Expected the proton file",
|
||||||
|
)
|
||||||
|
self.assertEqual(verb2, self.test_verb, "Expected a verb")
|
||||||
|
self.assertEqual(exe, self.env["EXE"], "Expected the EXE")
|
||||||
|
|
||||||
|
def test_set_env_toml_opts_nofile(self):
|
||||||
|
"""Test set_env_toml for options that are a file.
|
||||||
|
|
||||||
|
An error should not be raised if a launch argument is a file
|
||||||
|
We allow this behavior to give users flexibility at the cost of security
|
||||||
|
"""
|
||||||
|
test_toml = "foo.toml"
|
||||||
|
toml_path = self.test_file + "/" + test_toml
|
||||||
|
toml_str = f"""
|
||||||
|
[ulwgl]
|
||||||
|
prefix = "{self.test_file}"
|
||||||
|
proton = "{self.test_file}"
|
||||||
|
game_id = "{self.test_file}"
|
||||||
|
launch_args = ["{toml_path}"]
|
||||||
|
exe = "{self.test_exe}"
|
||||||
|
"""
|
||||||
|
result = None
|
||||||
|
|
||||||
|
Path(toml_path).touch()
|
||||||
|
|
||||||
|
with Path(toml_path).open(mode="w") as file:
|
||||||
|
file.write(toml_str)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# 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
|
||||||
|
ulwgl_plugins.set_env_toml(self.env, result)
|
||||||
|
|
||||||
|
# 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",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_set_env_toml_nofile(self):
|
||||||
|
"""Test set_env_toml for values that are not a file.
|
||||||
|
|
||||||
|
A FileNotFoundError should be raised if the 'exe' is not a file
|
||||||
|
"""
|
||||||
|
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 = "./bar"
|
||||||
|
"""
|
||||||
|
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(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# 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"):
|
||||||
|
ulwgl_plugins.set_env_toml(self.env, result)
|
||||||
|
|
||||||
|
def test_set_env_toml_err(self):
|
||||||
|
"""Test set_env_toml for valid TOML.
|
||||||
|
|
||||||
|
A TOMLDecodeError should be raised for invalid values
|
||||||
|
"""
|
||||||
|
test_toml = "foo.toml"
|
||||||
|
toml_str = f"""
|
||||||
|
[ulwgl]
|
||||||
|
prefix = [[
|
||||||
|
proton = "{self.test_file}"
|
||||||
|
game_id = "{self.test_file}"
|
||||||
|
launch_args = ["{self.test_file}", "{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(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# Args
|
||||||
|
result = ulwgl_run.parse_args()
|
||||||
|
self.assertIsInstance(
|
||||||
|
result, Namespace, "Expected a Namespace from parse_arg"
|
||||||
|
)
|
||||||
|
# Env
|
||||||
|
with self.assertRaisesRegex(TOMLDecodeError, "Invalid"):
|
||||||
|
ulwgl_plugins.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 the following keys are not dir: proton, prefix
|
||||||
|
"""
|
||||||
|
test_toml = "foo.toml"
|
||||||
|
toml_str = f"""
|
||||||
|
[ulwgl]
|
||||||
|
prefix = "foo"
|
||||||
|
proton = "foo"
|
||||||
|
game_id = "{self.test_file}"
|
||||||
|
launch_args = ["{self.test_file}", "{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(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# 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"):
|
||||||
|
ulwgl_plugins.set_env_toml(self.env, result)
|
||||||
|
|
||||||
|
def test_set_env_toml_tables(self):
|
||||||
|
"""Test set_env_toml for expected tables.
|
||||||
|
|
||||||
|
A ValueError should be raised if the following tables are absent: ulwgl
|
||||||
|
"""
|
||||||
|
test_toml = "foo.toml"
|
||||||
|
toml_str = f"""
|
||||||
|
[foo]
|
||||||
|
prefix = "{self.test_file}"
|
||||||
|
proton = "{self.test_file}"
|
||||||
|
game_id = "{self.test_file}"
|
||||||
|
launch_args = ["{self.test_file}", "{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(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# 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"):
|
||||||
|
ulwgl_plugins.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.
|
||||||
|
|
||||||
|
Example: ~/Games/foo.exe
|
||||||
|
An error should not be raised when passing unexpanded paths to the config file as well as the prefix, proton and exe keys
|
||||||
|
"""
|
||||||
|
test_toml = "foo.toml"
|
||||||
|
pattern = r"^/home/[\w\d]+" # Expects only unicode decimals and alphanumerics
|
||||||
|
|
||||||
|
# Replaces the expanded path to unexpanded
|
||||||
|
# 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()
|
||||||
|
path_to_exe = Path(
|
||||||
|
Path(__file__).cwd().as_posix() + "/" + self.test_exe
|
||||||
|
).as_posix()
|
||||||
|
|
||||||
|
# Replace /home/[a-zA-Z]+ substring in path with tilda
|
||||||
|
unexpanded_path = re.sub(
|
||||||
|
pattern,
|
||||||
|
"~",
|
||||||
|
path_to_tmp,
|
||||||
|
)
|
||||||
|
unexpanded_exe = re.sub(
|
||||||
|
pattern,
|
||||||
|
"~",
|
||||||
|
path_to_exe,
|
||||||
|
)
|
||||||
|
toml_str = f"""
|
||||||
|
[ulwgl]
|
||||||
|
prefix = "{unexpanded_path}"
|
||||||
|
proton = "{unexpanded_path}"
|
||||||
|
game_id = "{unexpanded_path}"
|
||||||
|
exe = "{unexpanded_exe}"
|
||||||
|
"""
|
||||||
|
# Path to TOML in unexpanded form
|
||||||
|
toml_path = unexpanded_path + "/" + test_toml
|
||||||
|
result = None
|
||||||
|
result_set_env = None
|
||||||
|
|
||||||
|
Path(toml_path).expanduser().touch()
|
||||||
|
|
||||||
|
with Path(toml_path).expanduser().open(mode="w") as file:
|
||||||
|
file.write(toml_str)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# 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 = ulwgl_plugins.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
|
||||||
|
# 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"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.env["PROTONPATH"],
|
||||||
|
unexpanded_path,
|
||||||
|
"Expected path not to be expanded",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.env["WINEPREFIX"],
|
||||||
|
unexpanded_path,
|
||||||
|
"Expected path not to be expanded",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.env["GAMEID"], unexpanded_path, "Expectd path not to be expanded"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_set_env_toml(self):
|
||||||
|
"""Test set_env_toml."""
|
||||||
|
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
|
||||||
|
|
||||||
|
Path(toml_path).touch()
|
||||||
|
|
||||||
|
with Path(toml_path).open(mode="w") as file:
|
||||||
|
file.write(toml_str)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
ulwgl_run,
|
||||||
|
"parse_args",
|
||||||
|
return_value=argparse.Namespace(config=toml_path),
|
||||||
|
):
|
||||||
|
# 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 = ulwgl_plugins.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(
|
||||||
|
self.env["EXE"],
|
||||||
|
self.test_exe + " " + " ".join([self.test_file, self.test_file]),
|
||||||
|
"Expectd GAMEID to be set",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.env["PROTONPATH"],
|
||||||
|
self.test_file,
|
||||||
|
"Expected PROTONPATH to be set",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.env["WINEPREFIX"],
|
||||||
|
self.test_file,
|
||||||
|
"Expected WINEPREFIX to be set",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.env["GAMEID"], self.test_file, "Expectd GAMEID to be set"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user