1 /*
2  * PROJECT:     ReactOS API tests
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Tests for One-Time initialization APIs
5  * COPYRIGHT:   Copyright 2023 Ratin Gao <ratin@knsoft.org>
6  */
7 
8 #include "precomp.h"
9 
10 typedef
11 VOID
12 WINAPI
13 FN_InitOnceInitialize(_Out_ PINIT_ONCE InitOnce);
14 
15 typedef
16 BOOL
17 WINAPI
18 FN_InitOnceExecuteOnce(
19     _Inout_ PINIT_ONCE InitOnce,
20     _In_ __callback PINIT_ONCE_FN InitFn,
21     _Inout_opt_ PVOID Parameter,
22     _Outptr_opt_result_maybenull_ LPVOID *Context);
23 
24 typedef
25 BOOL
26 WINAPI
27 FN_InitOnceBeginInitialize(
28     _Inout_ LPINIT_ONCE lpInitOnce,
29     _In_ DWORD dwFlags,
30     _Out_ PBOOL fPending,
31     _Outptr_opt_result_maybenull_ LPVOID *lpContext);
32 
33 typedef
34 BOOL
35 WINAPI
36 FN_InitOnceComplete(
37     _Inout_ LPINIT_ONCE lpInitOnce,
38     _In_ DWORD dwFlags,
39     _In_opt_ LPVOID lpContext);
40 
41 static ULONG g_ulRandom;
42 
43 static
44 VOID
45 InitWorker(_Inout_ PULONG InitCount, _Out_ PULONG_PTR Context)
46 {
47     /* Increase the initialization count */
48     (*InitCount)++;
49 
50     /* Output context data */
51     *Context = (ULONG_PTR)g_ulRandom;
52 }
53 
54 static
55 _Success_(return != FALSE)
56 BOOL
57 WINAPI
58 InitOnceProc(
59     _Inout_ PINIT_ONCE InitOnce,
60     _Inout_opt_ PVOID Parameter,
61     _Outptr_opt_result_maybenull_ PVOID* Context)
62 {
63     if (!Parameter || !Context)
64     {
65         return FALSE;
66     }
67 
68     InitWorker(Parameter, (PULONG_PTR)Context);
69     return TRUE;
70 }
71 
72 START_TEST(InitOnce)
73 {
74     BOOL bRet, fPending;
75     ULONG i, ulInitCount, ulSeed;
76     ULONG_PTR ulContextData, ulTempContext;
77     DWORD dwError;
78 
79     HMODULE hKernel32;
80     FN_InitOnceInitialize* pfnInitOnceInitialize;
81     FN_InitOnceExecuteOnce* pfnInitOnceExecuteOnce;
82     FN_InitOnceBeginInitialize* pfnInitOnceBeginInitialize;
83     FN_InitOnceComplete* pfnInitOnceComplete;
84 
85     /* Load functions */
86     hKernel32 = GetModuleHandleW(L"kernel32.dll");
87     if (!hKernel32)
88     {
89         skip("Module kernel32 not found\n");
90         return;
91     }
92     pfnInitOnceInitialize = (FN_InitOnceInitialize*)GetProcAddress(hKernel32, "InitOnceInitialize");
93     pfnInitOnceExecuteOnce = (FN_InitOnceExecuteOnce*)GetProcAddress(hKernel32, "InitOnceExecuteOnce");
94     pfnInitOnceBeginInitialize = (FN_InitOnceBeginInitialize*)GetProcAddress(hKernel32, "InitOnceBeginInitialize");
95     pfnInitOnceComplete = (FN_InitOnceComplete*)GetProcAddress(hKernel32, "InitOnceComplete");
96 
97     /*
98      * Use a random as output context data,
99      * which the low-order INIT_ONCE_CTX_RESERVED_BITS bits should be zero.
100      */
101     ulSeed = (ULONG)(ULONG_PTR)&ulSeed ^ GetTickCount();
102     g_ulRandom = RtlRandom(&ulSeed);
103     for (i = 0; i < INIT_ONCE_CTX_RESERVED_BITS; i++)
104     {
105         g_ulRandom &= (~(1 << i));
106     }
107 
108     /* Initialize One-Time initialization structure */
109     INIT_ONCE InitOnce = { (PVOID)(ULONG_PTR)0xDEADBEEF };
110     if (pfnInitOnceInitialize)
111     {
112         pfnInitOnceInitialize(&InitOnce);
113     }
114     else
115     {
116         skip("InitOnceInitialize not found\n");
117         InitOnce = (INIT_ONCE)INIT_ONCE_STATIC_INIT;
118     }
119 
120     if (!pfnInitOnceExecuteOnce)
121     {
122         skip("InitOnceExecuteOnce not found\n");
123         goto _test_sync;
124     }
125 
126     /*
127      * Perform synchronous initialization by using InitOnceExecuteOnce,
128      * which executes user-defined callback to initialize.
129      * Call InitOnceExecuteOnce twice will success,
130      * initialization count should be 1 and retrieve correct context data.
131      */
132     ulInitCount = 0;
133     ulContextData = MAXULONG;
134     bRet = pfnInitOnceExecuteOnce(&InitOnce, InitOnceProc, &ulInitCount, (LPVOID*)&ulContextData);
135     ok(bRet, "InitOnceExecuteOnce failed with %lu\n", GetLastError());
136     if (bRet)
137     {
138         /* Call InitOnceExecuteOnce again and check output values if the first call succeeded */
139         bRet = pfnInitOnceExecuteOnce(&InitOnce,
140                                       InitOnceProc,
141                                       &ulInitCount,
142                                       (LPVOID*)&ulContextData);
143         ok(bRet, "InitOnceExecuteOnce failed with %lu\n", GetLastError());
144         ok(ulInitCount == 1, "ulInitCount is not 1\n");
145         ok(ulContextData == g_ulRandom, "Output ulContextData is incorrect\n");
146     }
147 
148 _test_sync:
149     if (!pfnInitOnceBeginInitialize || !pfnInitOnceComplete)
150     {
151         skip("InitOnceBeginInitialize or InitOnceComplete not found\n");
152         return;
153     }
154 
155     /* Re-initialize One-Time initialization structure by using INIT_ONCE_STATIC_INIT */
156     InitOnce = (INIT_ONCE)INIT_ONCE_STATIC_INIT;
157     ulContextData = 0xdeadbeef;
158 
159     /* Perform synchronous initialization by using InitOnceBeginInitialize */
160     fPending = FALSE;
161     bRet = pfnInitOnceBeginInitialize(&InitOnce, 0, &fPending, (LPVOID*)&ulContextData);
162     ok(bRet, "InitOnceBeginInitialize failed with %lu\n", GetLastError());
163     if (!bRet)
164     {
165         goto _test_async;
166     }
167     ok(fPending, "fPending is not TRUE after the first success InitOnceBeginInitialize\n");
168     if (!fPending)
169     {
170         goto _test_async;
171     }
172 
173     /* Call again to check whether initialization has completed */
174     fPending = 0xdeadbeef;
175     bRet = pfnInitOnceBeginInitialize(&InitOnce,
176                                       INIT_ONCE_CHECK_ONLY,
177                                       &fPending,
178                                       (LPVOID*)&ulContextData);
179     ok(bRet == FALSE, "InitOnceBeginInitialize should fail\n");
180     ok(fPending == 0xdeadbeef, "fPending should be unmodified\n");
181     ok(ulContextData == 0xdeadbeef, "ulContextData should be unmodified\n");
182 
183     /* Complete the initialization */
184     InitWorker(&ulInitCount, &ulTempContext);
185     bRet = pfnInitOnceComplete(&InitOnce, 0, (LPVOID)ulTempContext);
186     ok(bRet, "InitOnceComplete failed with %lu\n", GetLastError());
187     if (!bRet)
188     {
189         goto _test_async;
190     }
191 
192     /*
193      * Initialization is completed, call InitOnceBeginInitialize with
194      * INIT_ONCE_CHECK_ONLY should retrieve status and context data successfully
195      */
196     bRet = pfnInitOnceBeginInitialize(&InitOnce,
197                                       INIT_ONCE_CHECK_ONLY,
198                                       &fPending,
199                                       (LPVOID*)&ulContextData);
200     ok(bRet && !fPending && ulContextData == g_ulRandom,
201        "InitOnceBeginInitialize returns incorrect result for a completed initialization\n");
202 
203 _test_async:
204     InitOnce = (INIT_ONCE)INIT_ONCE_STATIC_INIT;
205 
206     /* Perform asynchronous initialization */
207     fPending = FALSE;
208     bRet = pfnInitOnceBeginInitialize(&InitOnce,
209                                       INIT_ONCE_ASYNC,
210                                       &fPending,
211                                       (LPVOID*)&ulContextData);
212     ok(bRet, "InitOnceBeginInitialize failed with %lu\n", GetLastError());
213     if (!bRet)
214     {
215         return;
216     }
217     ok(fPending, "fPending is not TRUE after a success InitOnceBeginInitialize\n");
218     if (!fPending)
219     {
220         return;
221     }
222 
223     /*
224      * Now the initialization is in progress but not completed yet,
225      * call InitOnceBeginInitialize again without INIT_ONCE_ASYNC is invalid,
226      * should fail with ERROR_INVALID_PARAMETER
227      */
228     bRet = pfnInitOnceBeginInitialize(&InitOnce, 0, &fPending, (LPVOID*)&ulContextData);
229     ok(!bRet, "InitOnceBeginInitialize should not success\n");
230     if (!bRet)
231     {
232         dwError = GetLastError();
233         ok(dwError == ERROR_INVALID_PARAMETER,
234            "Last error is %lu, but %u is expected\n",
235            dwError,
236            ERROR_INVALID_PARAMETER);
237     }
238 
239     /*
240      * Call InitOnceBeginInitialize again with INIT_ONCE_ASYNC
241      * should success because initialization could be executed in parallel
242      */
243     bRet = pfnInitOnceBeginInitialize(&InitOnce,
244                                       INIT_ONCE_ASYNC,
245                                       &fPending,
246                                       (LPVOID*)&ulContextData);
247     ok(bRet, "InitOnceBeginInitialize failed with %lu\n", GetLastError());
248     if (!bRet)
249     {
250         return;
251     }
252     ok(fPending, "fPending is not TRUE after a success InitOnceBeginInitialize\n");
253     if (!fPending)
254     {
255         return;
256     }
257 
258     /* Complete the initialization once */
259     InitWorker(&ulInitCount, &ulTempContext);
260     bRet = pfnInitOnceComplete(&InitOnce, INIT_ONCE_ASYNC, (LPVOID)ulTempContext);
261     ok(bRet, "InitOnceComplete failed with %lu\n", GetLastError());
262     if (!bRet)
263     {
264         return;
265     }
266 
267     /* Subsequent InitOnceComplete should fail with ERROR_GEN_FAILURE */
268     bRet = pfnInitOnceComplete(&InitOnce, INIT_ONCE_ASYNC, (LPVOID)ulTempContext);
269     ok(!bRet, "InitOnceComplete should not success\n");
270     if (!bRet)
271     {
272         dwError = GetLastError();
273         ok(dwError == ERROR_GEN_FAILURE,
274            "Last error is %lu, but %u is expected\n",
275            dwError,
276            ERROR_GEN_FAILURE);
277     }
278 
279     /*
280      * Initialization is completed, call InitOnceBeginInitialize with
281      * INIT_ONCE_CHECK_ONLY should retrieve status and context data successfully
282      */
283     bRet = pfnInitOnceBeginInitialize(&InitOnce,
284                                       INIT_ONCE_CHECK_ONLY,
285                                       &fPending,
286                                       (LPVOID*)&ulContextData);
287     ok(bRet && !fPending && ulContextData == g_ulRandom,
288        "InitOnceBeginInitialize returns incorrect result for a completed initialization\n");
289 
290     return;
291 }
292