zenmaster 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: zenmaster
3
+ Version: 0.1.0
4
+ Summary: Pure-Python AMD Ryzen SMU power management for Linux and Windows
5
+ License: GPL-3.0-only
6
+ Keywords: amd,ryzen,ryzenadj,smu,power-management,tuning
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: End Users/Desktop
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: System :: Hardware
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7; extra == "dev"
26
+
27
+ # ZenMaster
28
+
29
+ [![PyPI](https://img.shields.io/pypi/v/zenmaster)](https://pypi.org/project/zenmaster/)
30
+ [![Python](https://img.shields.io/pypi/pyversions/zenmaster)](https://pypi.org/project/zenmaster/)
31
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
32
+ [![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Windows-lightgrey)](https://pypi.org/project/zenmaster/)
33
+
34
+ Pure-Python AMD Ryzen power management via SMU. Works on Linux and Windows. No compiler, no build chain, no dependencies.
35
+
36
+ ```bash
37
+ pip install zenmaster
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Features
43
+
44
+ - Set and read power limits, thermal limits, clock ranges, voltages, and PBO/Curve Optimiser offsets
45
+ - Live PM table — labeled power, thermal, and current sensor data
46
+ - Dynamic `--help` that shows only what your CPU supports
47
+ - JSON output for scripting and integration
48
+ - `--reapply=N` to continuously re-apply a tuning preset
49
+ - Embeddable Python library — use it as a backend in your own tuning tools
50
+ - **No WinRing0** — Windows support uses [PawnIO](https://github.com/namazso/PawnIO), a modern signed driver
51
+
52
+ ---
53
+
54
+ ## Why not RyzenAdj
55
+
56
+ | | RyzenAdj | ZenMaster |
57
+ |---|---|---|
58
+ | Install | Build from source | `pip install zenmaster` |
59
+ | Windows | WinRing0 ⚠️ | PawnIO ✅ |
60
+ | `--help` | Static, shows all args | Dynamic — CPU-specific only |
61
+ | Output | Plain text | Plain text or `--json` |
62
+ | PM table | Raw floats | Labeled fields with units |
63
+ | Embed / script | Shell out to binary | `import zenmaster` |
64
+ | Build deps | cmake, make, libpci | None |
65
+
66
+ ### On WinRing0
67
+
68
+ RyzenAdj's Windows backend uses WinRing0, a driver with well-documented security vulnerabilities ([CVE-2020-14979](https://nvd.nist.gov/vuln/detail/CVE-2020-14979), [CVE-2021-41285](https://nvd.nist.gov/vuln/detail/CVE-2021-41285)). It grants any unprivileged process full read/write access to physical memory, PCI config space, and I/O ports. Several AV vendors flag it as malicious outright.
69
+
70
+ ZenMaster uses [PawnIO](https://github.com/namazso/PawnIO) instead — a purpose-built, Microsoft-signed kernel driver that exposes a controlled IOCTL interface. No raw memory access, no known CVEs.
71
+
72
+ ---
73
+
74
+ ## Installation
75
+
76
+ ### Linux
77
+
78
+ ```bash
79
+ pip install zenmaster
80
+ ```
81
+
82
+ Requires root and either the `ryzen_smu` kernel module (recommended) or direct PCI access.
83
+
84
+ **Install ryzen_smu module:**
85
+
86
+ ```bash
87
+ git clone https://github.com/amkillam/ryzen_smu
88
+ cd ryzen_smu && make && sudo make install
89
+ sudo modprobe ryzen_smu
90
+ ```
91
+
92
+ **Apply a tuning preset:**
93
+
94
+ ```bash
95
+ sudo zenmaster --stapm-limit=15000 --fast-limit=20000 --tctl-temp=90
96
+ ```
97
+
98
+ **Re-apply every 30 seconds:**
99
+
100
+ ```bash
101
+ sudo zenmaster --stapm-limit=15000 --reapply=30
102
+ ```
103
+
104
+ ### Windows
105
+
106
+ 1. Install [PawnIO](https://github.com/namazso/PawnIO.Setup/releases/latest/download/PawnIO_setup.exe) and reboot.
107
+ 2. Open an **Administrator** command prompt or terminal.
108
+
109
+ ```bat
110
+ pip install zenmaster
111
+ zenmaster --stapm-limit=15000 --fast-limit=20000 --tctl-temp=90
112
+ ```
113
+
114
+ ---
115
+
116
+ ## CLI
117
+
118
+ ```
119
+ zenmaster [OPTIONS] [TUNING ARGS...]
120
+ ```
121
+
122
+ | Option | Description |
123
+ |---|---|
124
+ | `--help` | Show supported tuning args for your CPU |
125
+ | `--info` | Detected CPU name, family, socket, backend |
126
+ | `--table` | Live PM table with labeled values |
127
+ | `--dump-table` | Raw PM table floats with hex offsets |
128
+ | `--json` | Machine-readable JSON output |
129
+ | `--reapply=N` | Re-apply settings every N seconds |
130
+
131
+ **Example — check what your CPU supports:**
132
+
133
+ ```
134
+ $ zenmaster --help
135
+
136
+ ZenMaster — Ryzen Power Management Tool
137
+
138
+ Usage: zenmaster [OPTIONS] [TUNING ARGS...]
139
+
140
+ Tuning arguments for AMD Ryzen 9 7950X (Raphael, AM5_V1):
141
+
142
+ Power limits:
143
+ --stapm-limit=<mW> Sustained Power Limit — STAPM LIMIT
144
+ --fast-limit=<mW> Actual Power Limit — PPT LIMIT FAST
145
+ --slow-limit=<mW> Average Power Limit — PPT LIMIT SLOW
146
+ --stapm-time=<s> STAPM constant time
147
+ --slow-time=<s> Slow PPT constant time
148
+
149
+ Thermal:
150
+ --tctl-temp=<°C> Tctl Temperature Limit — THM LIMIT CORE
151
+ ...
152
+ ```
153
+
154
+ **Example — live PM table (APU/mobile):**
155
+
156
+ ```
157
+ $ sudo zenmaster --table
158
+
159
+ PM Table Version: 0x00450005
160
+ +-------------------------+-----------+------------------------+
161
+ | STAPM LIMIT | 15.000 | stapm-limit |
162
+ | STAPM VALUE | 12.441 | |
163
+ | PPT LIMIT FAST | 20.000 | fast-limit |
164
+ | PPT VALUE FAST | 18.203 | |
165
+ | THM LIMIT CORE | 90.000 | tctl-temp |
166
+ | THM VALUE CORE | 67.125 | |
167
+ +-------------------------+-----------+------------------------+
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Library usage
173
+
174
+ ZenMaster is designed to be embedded in tuning utilities, dashboards, and automation tools.
175
+
176
+ ```python
177
+ from zenmaster.hardware import detect
178
+ from zenmaster import smu
179
+ from zenmaster.apply import apply
180
+
181
+ # Detect CPU (no privileges needed)
182
+ info = detect()
183
+ print(info.name) # "AMD Ryzen 9 7950X"
184
+ print(info.family) # "Raphael"
185
+
186
+ # Initialise SMU backend — requires root/admin and a working driver.
187
+ # Always raises RuntimeError with a clear message if something is missing.
188
+ #
189
+ # Linux failure cases:
190
+ # - ryzen_smu not loaded + Secure Boot on → raises (lockdown blocks PCI too)
191
+ # - ryzen_smu not loaded + no PCI config → raises
192
+ # - ryzen_smu loaded but /smn missing → raises (module too old)
193
+ #
194
+ # Windows failure cases:
195
+ # - PawnIO not installed → raises with installer URL
196
+ # - PawnIO installed but not rebooted yet → raises, says to reboot
197
+ # - Not running as Administrator → raises
198
+ try:
199
+ backend = smu.init() # "ryzen_smu", "pci", or "pawnio"
200
+ except RuntimeError as e:
201
+ print(f"SMU unavailable: {e}")
202
+ raise SystemExit(1)
203
+
204
+ # Apply tuning args — returns per-arg results + a rejection flag
205
+ results, rejected = apply("--stapm-limit=15000 --tctl-temp=90", info.family)
206
+ for r in results:
207
+ print(r["arg"], r["status"]) # 0x01 = SMU_OK
208
+
209
+ # Read PM table (APU / mobile only)
210
+ if smu.pm_table_supported(info.family):
211
+ data = smu.read_pm_table(info.family)
212
+ ver = smu.read_pm_table_version(info.family)
213
+
214
+ # Send raw SMU commands directly
215
+ smu.send_mp1(info.family, opcode=0x05, arg0=15000)
216
+ smu.send_rsmu(info.family, opcode=0x53, arg0=90)
217
+ ```
218
+
219
+ **Look up supported args for a CPU:**
220
+
221
+ ```python
222
+ from zenmaster import runner
223
+
224
+ args = runner.get_supported_args("Raphael")
225
+ # ["stapm-limit", "fast-limit", "slow-limit", "tctl-temp", ...]
226
+
227
+ opcodes = runner.lookup("Raphael", "stapm-limit")
228
+ # [(True, 0x14), (False, 0x53)] — (is_mp1, opcode)
229
+ ```
230
+
231
+ **Install with dev dependencies:**
232
+
233
+ ```bash
234
+ pip install "zenmaster[dev]"
235
+ pytest
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Supported CPUs
241
+
242
+ Covers first-gen Ryzen (Summit Ridge / Zen 1) through Ryzen 9000 and Strix Halo. Run `zenmaster --info` to confirm detection and socket mapping.
243
+
244
+ PM table support (`--table`): Renoir, Lucienne, Cezanne/Barcelo, Rembrandt, Phoenix Point, Hawk Point, Strix Point, Krackan Point, Strix Halo.
245
+
246
+ ---
247
+
248
+ ## Requirements
249
+
250
+ | | Linux | Windows |
251
+ |---|---|---|
252
+ | Python | 3.10+ | 3.10+ |
253
+ | Privileges | root | Administrator |
254
+ | Driver | `ryzen_smu` module or PCI direct | [PawnIO](https://github.com/namazso/PawnIO.Setup) |
255
+ | Extra deps | None | None |
256
+
257
+ ---
258
+
259
+ ## License
260
+
261
+ GPL-3.0. SMU opcode tables from [UXTU4Linux](https://github.com/JamesCJ60/Universal-x86-Tuning-Utility-Handheld) (GPL-3.0). PawnIO kernel interface from [namazso/PawnIO](https://github.com/namazso/PawnIO) (MIT).
@@ -0,0 +1,235 @@
1
+ # ZenMaster
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/zenmaster)](https://pypi.org/project/zenmaster/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/zenmaster)](https://pypi.org/project/zenmaster/)
5
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
6
+ [![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Windows-lightgrey)](https://pypi.org/project/zenmaster/)
7
+
8
+ Pure-Python AMD Ryzen power management via SMU. Works on Linux and Windows. No compiler, no build chain, no dependencies.
9
+
10
+ ```bash
11
+ pip install zenmaster
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Features
17
+
18
+ - Set and read power limits, thermal limits, clock ranges, voltages, and PBO/Curve Optimiser offsets
19
+ - Live PM table — labeled power, thermal, and current sensor data
20
+ - Dynamic `--help` that shows only what your CPU supports
21
+ - JSON output for scripting and integration
22
+ - `--reapply=N` to continuously re-apply a tuning preset
23
+ - Embeddable Python library — use it as a backend in your own tuning tools
24
+ - **No WinRing0** — Windows support uses [PawnIO](https://github.com/namazso/PawnIO), a modern signed driver
25
+
26
+ ---
27
+
28
+ ## Why not RyzenAdj
29
+
30
+ | | RyzenAdj | ZenMaster |
31
+ |---|---|---|
32
+ | Install | Build from source | `pip install zenmaster` |
33
+ | Windows | WinRing0 ⚠️ | PawnIO ✅ |
34
+ | `--help` | Static, shows all args | Dynamic — CPU-specific only |
35
+ | Output | Plain text | Plain text or `--json` |
36
+ | PM table | Raw floats | Labeled fields with units |
37
+ | Embed / script | Shell out to binary | `import zenmaster` |
38
+ | Build deps | cmake, make, libpci | None |
39
+
40
+ ### On WinRing0
41
+
42
+ RyzenAdj's Windows backend uses WinRing0, a driver with well-documented security vulnerabilities ([CVE-2020-14979](https://nvd.nist.gov/vuln/detail/CVE-2020-14979), [CVE-2021-41285](https://nvd.nist.gov/vuln/detail/CVE-2021-41285)). It grants any unprivileged process full read/write access to physical memory, PCI config space, and I/O ports. Several AV vendors flag it as malicious outright.
43
+
44
+ ZenMaster uses [PawnIO](https://github.com/namazso/PawnIO) instead — a purpose-built, Microsoft-signed kernel driver that exposes a controlled IOCTL interface. No raw memory access, no known CVEs.
45
+
46
+ ---
47
+
48
+ ## Installation
49
+
50
+ ### Linux
51
+
52
+ ```bash
53
+ pip install zenmaster
54
+ ```
55
+
56
+ Requires root and either the `ryzen_smu` kernel module (recommended) or direct PCI access.
57
+
58
+ **Install ryzen_smu module:**
59
+
60
+ ```bash
61
+ git clone https://github.com/amkillam/ryzen_smu
62
+ cd ryzen_smu && make && sudo make install
63
+ sudo modprobe ryzen_smu
64
+ ```
65
+
66
+ **Apply a tuning preset:**
67
+
68
+ ```bash
69
+ sudo zenmaster --stapm-limit=15000 --fast-limit=20000 --tctl-temp=90
70
+ ```
71
+
72
+ **Re-apply every 30 seconds:**
73
+
74
+ ```bash
75
+ sudo zenmaster --stapm-limit=15000 --reapply=30
76
+ ```
77
+
78
+ ### Windows
79
+
80
+ 1. Install [PawnIO](https://github.com/namazso/PawnIO.Setup/releases/latest/download/PawnIO_setup.exe) and reboot.
81
+ 2. Open an **Administrator** command prompt or terminal.
82
+
83
+ ```bat
84
+ pip install zenmaster
85
+ zenmaster --stapm-limit=15000 --fast-limit=20000 --tctl-temp=90
86
+ ```
87
+
88
+ ---
89
+
90
+ ## CLI
91
+
92
+ ```
93
+ zenmaster [OPTIONS] [TUNING ARGS...]
94
+ ```
95
+
96
+ | Option | Description |
97
+ |---|---|
98
+ | `--help` | Show supported tuning args for your CPU |
99
+ | `--info` | Detected CPU name, family, socket, backend |
100
+ | `--table` | Live PM table with labeled values |
101
+ | `--dump-table` | Raw PM table floats with hex offsets |
102
+ | `--json` | Machine-readable JSON output |
103
+ | `--reapply=N` | Re-apply settings every N seconds |
104
+
105
+ **Example — check what your CPU supports:**
106
+
107
+ ```
108
+ $ zenmaster --help
109
+
110
+ ZenMaster — Ryzen Power Management Tool
111
+
112
+ Usage: zenmaster [OPTIONS] [TUNING ARGS...]
113
+
114
+ Tuning arguments for AMD Ryzen 9 7950X (Raphael, AM5_V1):
115
+
116
+ Power limits:
117
+ --stapm-limit=<mW> Sustained Power Limit — STAPM LIMIT
118
+ --fast-limit=<mW> Actual Power Limit — PPT LIMIT FAST
119
+ --slow-limit=<mW> Average Power Limit — PPT LIMIT SLOW
120
+ --stapm-time=<s> STAPM constant time
121
+ --slow-time=<s> Slow PPT constant time
122
+
123
+ Thermal:
124
+ --tctl-temp=<°C> Tctl Temperature Limit — THM LIMIT CORE
125
+ ...
126
+ ```
127
+
128
+ **Example — live PM table (APU/mobile):**
129
+
130
+ ```
131
+ $ sudo zenmaster --table
132
+
133
+ PM Table Version: 0x00450005
134
+ +-------------------------+-----------+------------------------+
135
+ | STAPM LIMIT | 15.000 | stapm-limit |
136
+ | STAPM VALUE | 12.441 | |
137
+ | PPT LIMIT FAST | 20.000 | fast-limit |
138
+ | PPT VALUE FAST | 18.203 | |
139
+ | THM LIMIT CORE | 90.000 | tctl-temp |
140
+ | THM VALUE CORE | 67.125 | |
141
+ +-------------------------+-----------+------------------------+
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Library usage
147
+
148
+ ZenMaster is designed to be embedded in tuning utilities, dashboards, and automation tools.
149
+
150
+ ```python
151
+ from zenmaster.hardware import detect
152
+ from zenmaster import smu
153
+ from zenmaster.apply import apply
154
+
155
+ # Detect CPU (no privileges needed)
156
+ info = detect()
157
+ print(info.name) # "AMD Ryzen 9 7950X"
158
+ print(info.family) # "Raphael"
159
+
160
+ # Initialise SMU backend — requires root/admin and a working driver.
161
+ # Always raises RuntimeError with a clear message if something is missing.
162
+ #
163
+ # Linux failure cases:
164
+ # - ryzen_smu not loaded + Secure Boot on → raises (lockdown blocks PCI too)
165
+ # - ryzen_smu not loaded + no PCI config → raises
166
+ # - ryzen_smu loaded but /smn missing → raises (module too old)
167
+ #
168
+ # Windows failure cases:
169
+ # - PawnIO not installed → raises with installer URL
170
+ # - PawnIO installed but not rebooted yet → raises, says to reboot
171
+ # - Not running as Administrator → raises
172
+ try:
173
+ backend = smu.init() # "ryzen_smu", "pci", or "pawnio"
174
+ except RuntimeError as e:
175
+ print(f"SMU unavailable: {e}")
176
+ raise SystemExit(1)
177
+
178
+ # Apply tuning args — returns per-arg results + a rejection flag
179
+ results, rejected = apply("--stapm-limit=15000 --tctl-temp=90", info.family)
180
+ for r in results:
181
+ print(r["arg"], r["status"]) # 0x01 = SMU_OK
182
+
183
+ # Read PM table (APU / mobile only)
184
+ if smu.pm_table_supported(info.family):
185
+ data = smu.read_pm_table(info.family)
186
+ ver = smu.read_pm_table_version(info.family)
187
+
188
+ # Send raw SMU commands directly
189
+ smu.send_mp1(info.family, opcode=0x05, arg0=15000)
190
+ smu.send_rsmu(info.family, opcode=0x53, arg0=90)
191
+ ```
192
+
193
+ **Look up supported args for a CPU:**
194
+
195
+ ```python
196
+ from zenmaster import runner
197
+
198
+ args = runner.get_supported_args("Raphael")
199
+ # ["stapm-limit", "fast-limit", "slow-limit", "tctl-temp", ...]
200
+
201
+ opcodes = runner.lookup("Raphael", "stapm-limit")
202
+ # [(True, 0x14), (False, 0x53)] — (is_mp1, opcode)
203
+ ```
204
+
205
+ **Install with dev dependencies:**
206
+
207
+ ```bash
208
+ pip install "zenmaster[dev]"
209
+ pytest
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Supported CPUs
215
+
216
+ Covers first-gen Ryzen (Summit Ridge / Zen 1) through Ryzen 9000 and Strix Halo. Run `zenmaster --info` to confirm detection and socket mapping.
217
+
218
+ PM table support (`--table`): Renoir, Lucienne, Cezanne/Barcelo, Rembrandt, Phoenix Point, Hawk Point, Strix Point, Krackan Point, Strix Halo.
219
+
220
+ ---
221
+
222
+ ## Requirements
223
+
224
+ | | Linux | Windows |
225
+ |---|---|---|
226
+ | Python | 3.10+ | 3.10+ |
227
+ | Privileges | root | Administrator |
228
+ | Driver | `ryzen_smu` module or PCI direct | [PawnIO](https://github.com/namazso/PawnIO.Setup) |
229
+ | Extra deps | None | None |
230
+
231
+ ---
232
+
233
+ ## License
234
+
235
+ GPL-3.0. SMU opcode tables from [UXTU4Linux](https://github.com/JamesCJ60/Universal-x86-Tuning-Utility-Handheld) (GPL-3.0). PawnIO kernel interface from [namazso/PawnIO](https://github.com/namazso/PawnIO) (MIT).
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zenmaster"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.10"
9
+ description = "Pure-Python AMD Ryzen SMU power management for Linux and Windows"
10
+ readme = "README.md"
11
+ license = { text = "GPL-3.0-only" }
12
+ keywords = ["amd", "ryzen", "ryzenadj", "smu", "power-management", "tuning"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Environment :: Console",
16
+ "Intended Audience :: End Users/Desktop",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Operating System :: Microsoft :: Windows",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: System :: Hardware",
28
+ "Topic :: Utilities",
29
+ ]
30
+
31
+ [project.scripts]
32
+ zenmaster = "zenmaster.cli:main"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=7"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["."]
42
+ include = ["zenmaster*"]
43
+
44
+ [tool.setuptools.package-data]
45
+ zenmaster = ["assets/AMD/PawnIO/*.bin"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,62 @@
1
+ from unittest.mock import patch
2
+ from zenmaster.apply import apply
3
+ from zenmaster.smu import SMU_OK, SMU_FAILED
4
+
5
+
6
+ def test_apply_single_arg_ok():
7
+ with patch("zenmaster.smu.send_mp1", return_value=SMU_OK), \
8
+ patch("zenmaster.smu.send_rsmu", return_value=SMU_OK):
9
+ results, rejected = apply("--stapm-limit=15000", "Rembrandt")
10
+ assert not rejected
11
+ assert any(r["arg"] == "stapm-limit" and r["status"] == SMU_OK for r in results)
12
+
13
+
14
+ def test_apply_unsupported_arg():
15
+ results, rejected = apply("--nonexistent-arg=1", "Rembrandt")
16
+ assert any(r["arg"] == "nonexistent-arg" and r["status"] == 0 for r in results)
17
+
18
+
19
+ def test_apply_unknown_family():
20
+ results, rejected = apply("--stapm-limit=15000", "UnknownCPU")
21
+ assert any(r["arg"] == "stapm-limit" and r["status"] == 0 for r in results)
22
+
23
+
24
+ def test_apply_rejected_counts_as_rejection():
25
+ with patch("zenmaster.smu.send_mp1", return_value=SMU_FAILED), \
26
+ patch("zenmaster.smu.send_rsmu", return_value=SMU_FAILED):
27
+ results, rejected = apply("--stapm-limit=15000", "Rembrandt")
28
+ assert rejected
29
+
30
+
31
+ def test_apply_skin_temp_scale():
32
+ captured = []
33
+ def fake_send_mp1(family, op, arg0):
34
+ captured.append(arg0)
35
+ return SMU_OK
36
+ with patch("zenmaster.smu.send_mp1", side_effect=fake_send_mp1), \
37
+ patch("zenmaster.smu.send_rsmu", return_value=SMU_OK):
38
+ apply("--apu-skin-temp=45", "Rembrandt")
39
+ assert any(v == 45 * 256 for v in captured)
40
+
41
+
42
+ def test_apply_multiple_args():
43
+ with patch("zenmaster.smu.send_mp1", return_value=SMU_OK), \
44
+ patch("zenmaster.smu.send_rsmu", return_value=SMU_OK):
45
+ results, rejected = apply("--stapm-limit=15000 --tctl-temp=85", "Rembrandt")
46
+ arg_names = [r["arg"] for r in results]
47
+ assert "stapm-limit" in arg_names
48
+ assert "tctl-temp" in arg_names
49
+
50
+
51
+ def test_apply_empty_string():
52
+ results, rejected = apply("", "Rembrandt")
53
+ assert results == []
54
+ assert not rejected
55
+
56
+
57
+ def test_apply_strips_leading_dashes():
58
+ with patch("zenmaster.smu.send_mp1", return_value=SMU_OK), \
59
+ patch("zenmaster.smu.send_rsmu", return_value=SMU_OK):
60
+ r1, _ = apply("--stapm-limit=15000", "Rembrandt")
61
+ r2, _ = apply("stapm-limit=15000", "Rembrandt")
62
+ assert len(r1) == len(r2)