11// TODO: upgrade packages so that eslint recognizes vitest globals out of the box
22import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
3- import { getCall , setCall } from "../native/index.js " ;
3+ import { getCall , setCall } from "../native" ;
44import { state } from "../state/index.js" ;
55import {
66 fanControl ,
77 fanPercentToSpeed ,
88 CYCLE_DURATION ,
99 WAIT_RAMP_UP_CYCLES ,
10+ WAIT_RAMP_DOWN_CYCLES ,
1011} from "./index.js" ;
1112
1213vi . mock ( "../native" , ( ) => ( {
@@ -32,21 +33,29 @@ function mockTemperatures(cpu: number, gpu: number) {
3233}
3334
3435async function waitUntilFanPercent ( fanPercent : number ) {
35- let elapsed = 0 ;
36- const interval = CYCLE_DURATION ;
37- const timeout = interval * 20 ;
38-
39- mockedSetCall . mockClear ( ) ;
40- while (
41- elapsed < timeout &&
42- mockedSetCall . mock . calls [ 0 ] ?. [ 2 ] . Data !== fanPercentToSpeed ( fanPercent )
43- ) {
44- await vi . advanceTimersByTimeAsync ( interval ) ;
45- elapsed += interval ;
46- }
36+ let advancedTime = 0 ;
37+
38+ while ( true ) {
39+ try {
40+ expect ( mockedSetCall ) . toHaveBeenLastCalledWith (
41+ expect . any ( String ) ,
42+ expect . any ( String ) ,
43+ {
44+ Data : fanPercentToSpeed ( fanPercent ) ,
45+ } ,
46+ ) ;
47+ return advancedTime / CYCLE_DURATION ;
48+ } catch ( _ ) {
49+ // console.log(
50+ // `Last call: ${
51+ // mockedSetCall.mock.calls[mockedSetCall.mock.calls.length - 1][2]
52+ // .Data
53+ // } (Expected: ${fanPercentToSpeed(fanPercent)})`,
54+ // );
55+ }
4756
48- if ( elapsed >= timeout ) {
49- throw new Error ( "Timeout - function never returned true." ) ;
57+ await vi . advanceTimersByTimeAsync ( 10 ) ;
58+ advancedTime += 10 ;
5059 }
5160}
5261
@@ -126,20 +135,47 @@ describe("fan-control", () => {
126135 ]
127136 ` ) ;
128137
129- // High CPU temperature
138+ // High CPU temperature => 50% fan speed
130139 mockTemperatures ( 90 , 30 ) ;
131- await waitUntilFanPercent (
132- state . cpuFanTable [ state . cpuFanTable . length - 1 ] [ 1 ] ,
140+ await vi . advanceTimersByTimeAsync (
141+ 3 * WAIT_RAMP_UP_CYCLES * CYCLE_DURATION + 1000 ,
142+ ) ;
143+ expect ( mockedSetCall ) . toHaveBeenLastCalledWith (
144+ expect . any ( String ) ,
145+ expect . any ( String ) ,
146+ {
147+ Data : fanPercentToSpeed (
148+ state . cpuFanTable [ state . cpuFanTable . length - 1 ] [ 1 ] ,
149+ ) ,
150+ } ,
133151 ) ;
134152
135- // Cool CPU
153+ // Cool CPU => 15% fan speed
136154 mockTemperatures ( 30 , 30 ) ;
137- await waitUntilFanPercent ( state . cpuFanTable [ 0 ] [ 1 ] ) ;
155+ await vi . advanceTimersByTimeAsync (
156+ 3 * WAIT_RAMP_DOWN_CYCLES * CYCLE_DURATION + 1000 ,
157+ ) ;
158+ expect ( mockedSetCall ) . toHaveBeenLastCalledWith (
159+ expect . any ( String ) ,
160+ expect . any ( String ) ,
161+ {
162+ Data : fanPercentToSpeed ( state . cpuFanTable [ 0 ] [ 1 ] ) ,
163+ } ,
164+ ) ;
138165
139- // High GPU temperature
166+ // High GPU temperature => 100% fan speed
140167 mockTemperatures ( 30 , 90 ) ;
141- await waitUntilFanPercent (
142- state . gpuFanTable [ state . gpuFanTable . length - 1 ] [ 1 ] ,
168+ await vi . advanceTimersByTimeAsync (
169+ 5 * WAIT_RAMP_UP_CYCLES * CYCLE_DURATION + 1000 ,
170+ ) ;
171+ expect ( mockedSetCall ) . toHaveBeenLastCalledWith (
172+ expect . any ( String ) ,
173+ expect . any ( String ) ,
174+ {
175+ Data : fanPercentToSpeed (
176+ state . gpuFanTable [ state . gpuFanTable . length - 1 ] [ 1 ] ,
177+ ) ,
178+ } ,
143179 ) ;
144180 } ) ;
145181
@@ -149,26 +185,51 @@ describe("fan-control", () => {
149185 state . doFixedSpeed = true ;
150186 state . fixedPercentage = 75 ;
151187
152- await waitUntilFanPercent ( state . fixedPercentage ) ;
188+ await vi . advanceTimersByTimeAsync ( CYCLE_DURATION ) ;
189+ expect ( mockedSetCall ) . toHaveBeenLastCalledWith (
190+ expect . any ( String ) ,
191+ expect . any ( String ) ,
192+ {
193+ Data : fanPercentToSpeed ( state . fixedPercentage ) ,
194+ } ,
195+ ) ;
153196 } ) ;
154197
155- it ( "fan speed adjusts after X cycles" , async ( ) => {
198+ it ( "fan speed adjusts gradually after temperature change" , async ( ) => {
199+ // We can't just repeatedly advance the timer in this test because execution times aren't perfect and when allowing for a bit of leeway, we would cut into the time for the next gradient step.
200+
156201 fanControl ( ) ;
157- // Make sure steady state is reached
158- await vi . advanceTimersByTimeAsync ( CYCLE_DURATION * 10 ) ;
202+ // Make sure steady state is reached at initial temperature
203+ await waitUntilFanPercent ( state . cpuFanTable [ 0 ] [ 1 ] ) ;
159204
205+ // Change to high CPU temperature
160206 mockTemperatures ( 90 , 30 ) ;
161207 vi . clearAllMocks ( ) ;
162- // Cycle 1: Likely mix of temperatures
163- // Cycle 2: Temp has become stable => target will be changed
164- // Cycles 3+: WAIT_RAMP_UP_CYCLES until target will actually be applied
165- await vi . advanceTimersByTimeAsync (
166- CYCLE_DURATION * ( WAIT_RAMP_UP_CYCLES + 2 ) ,
167- ) ;
168208
169- expect ( mockedSetCall . mock . calls [ 0 ] [ 2 ] . Data ) . toEqual (
170- fanPercentToSpeed ( state . cpuFanTable [ state . cpuFanTable . length - 1 ] [ 1 ] ) ,
171- ) ;
209+ const initialPercentage = state . cpuFanTable [ 0 ] [ 1 ] ;
210+ const targetPercentage = state . cpuFanTable [ state . cpuFanTable . length - 1 ] [ 1 ] ;
211+
212+ // First gradient step
213+ let currentPercentage = initialPercentage ;
214+ let expectedPercentage =
215+ currentPercentage +
216+ Math . round ( ( targetPercentage - currentPercentage ) / 2 ) ;
217+ let cycles = await waitUntilFanPercent ( expectedPercentage ) ;
218+ expect ( cycles - WAIT_RAMP_UP_CYCLES ) . toBeLessThan ( 1 ) ;
219+
220+ // Second gradient step
221+ currentPercentage = expectedPercentage ;
222+ expectedPercentage =
223+ currentPercentage +
224+ Math . round ( ( targetPercentage - currentPercentage ) / 2 ) ;
225+ cycles = await waitUntilFanPercent ( expectedPercentage ) ;
226+ expect ( cycles - WAIT_RAMP_UP_CYCLES ) . toBeLessThan ( 1 ) ;
227+
228+ // Reaching target
229+ currentPercentage = expectedPercentage ;
230+ expectedPercentage = targetPercentage ;
231+ cycles = await waitUntilFanPercent ( expectedPercentage ) ;
232+ expect ( cycles - WAIT_RAMP_UP_CYCLES ) . toBeLessThan ( 1 ) ;
172233 } ) ;
173234
174235 it ( "should handle fan table changes" , async ( ) => {
0 commit comments